R0ot's Blog

分享代码,记录生活

0%

命令执行与代码执行漏洞

命令执行漏洞

原理:开发者为了实现网站的一些特殊功能时(比如ping命令)需要使用一些特殊函数,这些函数可以执行系统命令,如果没有进行严格的过滤,那么用户就可以通过这个函数实现一些危险的命令,或者加入一些危险的参数,从而获取信息

危险函数

这些函数如果可以被用户注入命令,就会带来危险性,写了个大概意思,具体参考php文档

system()

  1. 作用:
    执行外部程序,并且显示输出
    system()函数用于向操作系统传递控制台命令行,以Windows系统为例,通过system()函数执行命令和在cmd窗口中执行命令的效果是一样的
  2. 注意
    命令必须是字符串的形式

exec()

与system()类似,执行系统命令,返回最后一行数据,但不显示输出

shell_exec()

通过shell执行命令,返回全部数据,但不显示输出,这个函数等同于执行运算符(反引号)

``(反引号)

在php中称之为执行运算符,PHP将尝试将反引号中的内容作为shell命令来执行,并返回数据

passthru()

passthru直接将结果输出到浏览器,不返回数据,且其可以输出二进制,比如图像数据。

popen(command,mode)

打开进程文件指针
函数需要两个参数,执行命令:command,文件的连接模式:mode,有r和w代表读和写。
函数会执行command命令,但不会返回执行结果,而是返回一个文件指针,想要去获取命令执行的结果,就类似于操作文件去获取

1
2
3
4
5
6
7
8
<?php    
$fp = popen("whoami","r"); //popen打开一个进程通道,类似于$fp=fopen(),命令结果在这个文件里
while (!feof($fp)) { //判断是否到EOF
$out = fgets($fp); //将命令的结果逐行遍历
echo $out; //打印出结果
}
pclose($fp); //关闭通道,类似于关闭文件
?>

proc_open()

执行一个命令,并且打开用来输入/输出的文件指针。
类似popen()函数,但是proc_open()提供了更加强大的控制程序执行的能力。

pcntl_exec()

在当前进程空间执行指定程序

常用命令

Linux

  1. cat
    命令用于连接文件并打印到标准输出设备上,可以知道文件内容,但是经常被过滤吧
  2. cd:切换当前工作目录;
    用法:cd 后面加目录
  3. ls:显示指定工作目录下的内容;
    用法:ls 文件或路径
  4. dir: 显示指定工作目录下的内容
    ls类似,同于ls -C -b,在ls被过滤时是一个不错的选择
  5. cp:拷贝文件;
    用法:cp 要复制的文件 目标路径/新的名字
  6. echo:输出
    用法:输入什么就打印什么,可以配合管道符|使用,也可以配合反引号内联执行
    echo > 1.txt 将输出的内容重定向到文件中,表示覆盖后写入
    echo >> 1.txt:将输出的内容重定向到文件中,表示追加写入
  7. **find [path] [expression]**:
    查找指定文件;用法:find 目录 -name 文件
    path是要查找的目录路径,可以是一个目录或文件名,也可以是多个路径,如果未指定路径,则默认为当前目录
    expression 是可选参数,用于指定查找的条件,可以是文件名、文件类型、文件大小等等。
    例如:find /home -name “*.txt”
    查找/home目录下,所有以.txt 结尾的文件或者目录
    -name:是按文件名查找,支持使用通配符 * 和 ?。
  8. pwd
    显示当前所在的目录
  9. more:
    命令类似cat,不过会以一页一页的形式显示
  10. less:
    more类似
  11. head:
    查看头几行
  12. tac:
    从最后一行开始显示,可以看出 tac 是 cat 的反向显示(是从最后一行输出,不是每一行逆序输出)
  13. tail:
    查看尾几行
  14. nl
    可以显示出文件内容的,而且输出行号
  15. od:
    od指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来,八进制三个为一组
    使用od -c可以使内容以原本的形式显示
  16. vi:
    文本编辑器,这个也可以查看
  17. vim:
    Vim是从vi发展出来的一个文本编辑器,这个也可以查看
    可以去菜鸟教程详细学习
  18. sort:
    sort可针对文本文件的内容,以行为单位来排序,可以查看文件
  19. uniq:
    uniq 可检查文本文件中重复出现的行列,可以查看文件,但是重复内容只会显示一个
  20. file -f:
    会将文件内容以报错的形式显示出来
  21. grep:
    用于查找文件里符合条件的字符串或正则表达式,并打印匹配的行:
    例如:
    grep a 1.txt 在文件1.txt中查找字符”a”,并打印匹配的行:
    grep test *txt 表示在当前目录中,包含test字符串的txt文件,,并打印出该字符串的行。

Windows

cd:切换目录
D::跳转到其他硬盘
ping:测试IP
ipconfig:查看网络详情,类似于linux的ifconfig
dir:显示目录中的文件内容,类似于linux的ls
type:查看文件,类似于linux的cat、less、more;用法:type 文件名
md:创建文件夹,类似于linux的mkdir;用法:md 目录名
tree:查看目录结构
copy:复制文本文件;用法:copy 文件1 文件2
注:
参数/b指定以二进制格式复制、合并文件,用于图像类/声音类文件
参数/a指定以ASCII格式复制、合并文件,用于txt等文档类文件
net start 服务名;net stop 服务名
cls:清空cmd命令行,类似于linux的clear
ctrl+C:结束或退出cmd正在执行的脚本
find:查找
find /c “所要搜索的文件所包含的字符串” 文件的绝对路径
for:对一个或一组文件,字符串或命令结果中的每一个对象执行特定命令
(1)找出C盘下的所有文件,并将所有文件名都输出出来
for /r C: %i in () do @echo %i
(2)找出C盘下所有后缀是.txt的文件,并将其输出
for /r C: %i in (
.txt) do @echo %i
(3)找出C盘下所有后缀是.txt和.jpg的文件,并将其输出
for /r C: %i in (.txt,.jpg) do @echo %i

命令分隔符(连接符)

命令分隔符,就是用来分隔命令的,在两句命令中加入不同的符号,效果也不相同,在Windows和Linux两种不同的操作系统中也略有区别
我们主要用分割符来拼接语句,所以,我们更关心后面的语句能否执行

  1. |
    在Windows中,会直接执行后面的语句,但如果前段语句为假,则后面的语句也无法执行
    在Linux中,是作为管道符,管道符左边命令的输出就会作为管道符右边命令的输入,无论前面真假,显示后面的执行结果
  2. &
    无论前面的语句的真假,后面的语句都可以执行
  3. &&
    前面为假,则后面不执行
  4. ||
    前面为真,则后面不执行
  5. ;分号
    Linux特有的命令连接,前面执行完前面的语句再执行后面的,也就是说,无论前面的真假,后面都会执行

通配符

  1. 作用
    通配符用来模糊搜索文件。当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正的字符,或者在ctf中,关键字被过滤,也可以用通配符来替代
  2. ?
    匹配一个任意字符,如fl?g可以匹配成flag,flbg
  3. *
    匹配0个或多个任意字符,也就是可以匹配任意内容,如f*g,匹配成以f开头,g结尾的所有字符
  4. []
    Linux里有这个通配符,而Windows没有
    匹配括号中指定的任意一个字符,如fl[abc]g可以匹配到flag,flbg,flcg
    扩展:
    [-] 匹配括号中范围内的任意一个字符,如a-c代表范围a到c
    [^] 逻辑非,表示匹配不是括号内的一个字符
  5. 注意
    反斜杠\或单引号'双引号"都会使通配符失效。
    如: \*, "*", '*'都表示*本身,不通配任何文件。

代码执行

危险函数

eval()

严格来说,php中eval()不是函数,而是一个语言结构,所以无法变量函数(可变函数)函数的方式调用.
效果:将括号内的作为php代码执行.
变量函数参考

assert()

如果括号内是字符串将会当成php代码执行
注意:在php7.2之前,assert()是一个函数,可以使用变量(可变)函数调用.在此之后assert也同eval,是语言解构而不是函数.

preg_replace(参1,参2,参3)+/e

执行一个正则表达式的搜索和替换,注意,php7.0之后,不再支持/e模式

  1. 效果:
    搜索参数3中是否有参数1,如果有则用参数2进行替换
  2. /e模式:
    /e模式下,可能会导致代码执行漏洞,将参数1写成/参数1/e的形式,如果发生了替换,参数2就会被当成php代码,最后替换成代码执行后的结果
    例如:preg_replace($_GET[a],$_GET[b],$_GET[c])
    payload:?a=/hello/e&b=phpinfo()&c=hello world //替换时会执行phpinfo

create_function(参1,参2)

  1. 函数作用
    根据传递的参数创建匿名函数,并为其返回唯一名称。参1声明函数的变量部分,参2是执行的方法代码部分
    简单来说,这个函数就是类似于function一个函数,函数1就是这个函数里面的变量,参数二的部分会被当做function函数内的代码执行
  2. 利用函数执行代码
    其实感觉原理和sql注入有点像,先将前面的语句闭合,再写入想要执行的代码,最后将后面的语句注释
    前提是,参数二是用变量拼接的,如$c='echo '.'($s'.'+'."$a)".';';
    例如,$a,$b是_GET传参的
    1
    2
    3
    4
    5
    6
    <?php
    $a=$_GET[a];
    $c='echo '.'($s'.'+'."$a)".';';
    $New=create_function('$s',$c);
    $New(100);
    ?>
    实际效果:
    1
    2
    3
    function New{
    echo (100+$a);
    }
    那我们构造一个payload:?a=888);}phpinfo();/*
    原函数变成:
    1
    2
    3
    4
    5
    function new{
    echo (100+888);}
    phpinfo();
    /*);
    }

array_map(参1,参2)

为数组的每个元素应用回调函数
参数1为回调函数(可以是自己定义的,也可以是php自带的),参数2是一个数组,每个元素将会被作为命令放在回调函数里执行
例如:

1
2
3
4
<?php
$array[0]='whoami';
array_map('system',$array);
?>

call_user_func(参1,参2)

和array_map()相似,参数1为回调函数,参数2是一个变量,作为回调函数的参数或命令
再给个例子payload=?a=phpinfo()

1
2
3
<?php 
call_user_func(assert,$_GET['a']);
?>

call_user_func_array(参数1,参数2)

和array_map()相似,参数1是回调函数,参数2为数组

array_filter(参数1,参数2)

使用回调函数过滤数组的元素
参数1是一个数组,参数2是回调函数

usort()

使用用户自定义的比较函数对数组中的值进行排序
先贴一个别人的,有点没看懂

1
2
3
4
5
6
7
8
9
10
php环境>=5.6才能用
<?php usort(...$_GET);?>
利用方式:
test.php?1[]=1-1&1[]=eval($_POST['x'])&2=assert
[POST]:x=phpinfo();
php环境>=<5.6才能用
<?php usort($_GET,'asse'.'rt');?>
利用方式:
test.php?1=1+1&2=eval($_POST[x])
[POST]:x=phpinfo();

uasort()

使用用户定义的比较函数对数组进行排序并保持索引关联

获取信息

如果达成了代码执行的条件的情况下,首先可以试着执行phpinfo();,并在页面查找是否有flag{,如果没有,再观察disable_functions部分有哪些函数被禁用

达成了代码执行的效果之后,一般分为几种情况
1.代码执行的参数过滤函数(如命令执行的危险函数)
2.有些函数被禁用disable_functions
3.过滤了字母,或是一些特殊字符

首先要明确的是,如果命令执行的函数不能用了,还可以考虑文件读取函数
读取文件用的函数

1
2
3
4
5
highlight_file()
show_source()
readfile()
echo + file_get_contents()
print_r() + file()

需要注意的是,如果函数仅仅是被过滤了,那么我们可以试着”转移矛盾
但是如果函数是被禁用了,就彻底没办法用了(也许可以试一下蚁剑的插件)

过滤了字母和特殊字符时,先考虑取反绕过异或绕过
如果禁用的是~^和一些关键字,可以采用scandir()数组中找需要的函数

读取目录的函数

1
2
3
4
5
scandir()
//遍历目录,可以配合print_r以数组的形式输出,例如print_r(scandir('/'));可以打印出根目录下的所有文件
getcwd() //获取当前目录
dirname() //返回上级目录
chdir() //更改当前目录

数组的控制

1
2
3
4
5
6
7
next() //将内部指针指向数组中的下一个元素,并返回下一个元素的值。
array_reverse() //将数组进行倒序
end() //将数组的内部指针移动到最后一个元素,并返回该元素的值
current() //是PHP中的一个函数,它用于返回数组中内部指针当前指向的元素的值
array_rand() //从数组中取出一个或多个随机的单元,并返回随机条目的一个或多个键。
array_flip() //array_flip()返回一个反转后的array,例如array中的键名变成了值,而array中的值成了键名。
pos() //传入数组,回显第一个数组的值,pos可以用current代替

随机读取当前目录下的一个文件
show_source(array_rand(array_flip(scandir(current(localeconv())))));