【BUUCTF】【WEB】Nmap
考点:escapeshellarg() + escapeshellcmd()组合参数注入漏洞;Nmap 命令参数;Bash 字符串解析规则;错误信息泄露的利用。
打开题目:
尝试输入127.0.0.1尝尝咸淡,发现这个页面貌似什么有用的信息都没有。
回到主界面,尝试输入一个单引号':
有个result.php文件,同时发现主机被“吃掉”了,这代表着我们输入的'被转义了。
这证明了有escapeshellarg()函数的存在。
为什么呢?
因为escapeshellarg()的作用是:确保输入作为一个完整的参数,详细描述就是把任意输入变成一个 "安全的、完整的单个 shell 参数"。
至于具体工作原理......这里留个悬念,后面在解释。
尝试输入' -v '后,发现主机还是被“吃掉”了:
' -v '由六个字符组成,解释一下各个字符的含义:
- ' 提前关闭后台
escapeshellarg()给我们输入加上的单引号 - -v Nmap本身的一个参数,用于打印版本信息
- ' 语法闭合符,闭合后台
escapeshellarg()给我们输入末尾加上的单引号
当输入' -v '后,主机仍被"吃掉",就代表这另一个函数escapeshellcmd()也存在。
为什么呢,因为escapeshellcmd()的作用是:转义了单引号字符串内部的反斜杠
目前可以确定是escapeshellarg() + escapeshellcmd()组合漏洞
现在讲明整个paylaod的处理过程:
先说escapeshellarg()的算法:
- 用一对单引号
' '把整个输入包裹起来 - 把输入中所有的单引号
'都替换成'\''
也就是说当输入' -v '时,经过escapeshellarg()的处理,将
' -v ' //发现没有,格式不是'-v'转化为(注意:是两对单引号,不是一对双引号)。至于单引号中间为什么有空格,先填个坑......
' ' -v ' '再把中间的两个单引号替换为'\''
' '\'' -v '\'' '再说escapeshellcmd()的算法:
escapeshellcmd()会扫描整个字符串,不管上下文,只要看到它认为是 "shell 特殊字符" 的东西,就在前面加一个反斜杠:
escapeshellcmd()认为的特殊字符包括:' " \ $ | & ; ( ) < >等,它会将:
' '\'' -v '\'' '它看到了两个反斜杠:
' '\\'' -v '\\'' '这里有个问题,为什么最终结果是执行成功呢?
经过两次转义后,关键部分是:
' '\\'' & '\\'' 'Bash是什么?所有的命令注入漏洞,本质上都是利用了 Bash 的解析规则。
Bash 会做这几件事:
- 解析:把你输入的字符串拆分成 "命令" 和 "参数"
- 翻译:把人类能看懂的命令翻译成内核能执行的机器指令
- 执行:调用操作系统内核执行对应的操作
- 返回:把执行结果显示在屏幕上
所以——
Bash 逐字符解析过程
| 字符序号 | 字符 | 当前解析状态 | 解析动作 | 结果 |
|---|---|---|---|---|
| 1 | ' | 初始状态 | 进入单引号字符串模式 | - |
| 2 | 空格 | 单引号模式 | 普通字符,保留 | |
| 3 | ' | 单引号模式 | 遇到单引号,退出单引号模式 | - |
| 4 | \ | 无引号模式 | 反斜杠转义后面的一个字符 | - |
| 5 | \ | 无引号模式 | 被前面的反斜杠转义,变成一个普通的\ | \ |
| 6 | ' | 无引号模式 | 普通的单引号字符 | ' |
| 7 | ' | 无引号模式 | 遇到单引号,重新进入单引号模式 | - |
我们可以发现
escapeshellcmd()加的那个额外的反斜杠,跑到了单引号字符串的外面- 在无引号上下文中,
\\被解析为一个普通的\ - 然后后面的
'在无引号上下文中,就是一个普通的单引号
所以整个字符串:
' '\\'' -v '\\'' '在 Bash 中最终被解析为:
' '\'' -v '\'' '也就是:
' ' -v ' '两个连续的单引号''在 Bash 中就是一个空字符串,所以整个命令等价于:
nmap -v按理说会出现版本信息对吧?那为什么没有显示版本信息呢?
这是因为Nmap 的命令行参数解析规则和后台默认参数的顺序导致的。
Nmap 的命令行语法是绝对严格的:
nmap [扫描类型] [选项参数] {目标}所有选项参数(比如-v、-iL、-o等)必须放在目标的前面。任何放在目标后面的内容,即使以-开头,也会被 Nmap 当作另一个扫描目标,而不是选项。
对比一下漏洞是否存在的区别
| 情况 | 输入 | Nmap的行为 | 页面显示 |
| 漏洞存在: | ' -v ' | 尝试扫描空字符串作为目标 | Host maybe down |
| 漏洞不存在: | ' -v ' | 尝试扫描整个字符串作为目标 | Failed to resolve "' -v '" |
这个漏洞的本质是:
escapeshellarg()创建了一个安全的单引号字符串escapeshellcmd()愚蠢地转义了单引号字符串内部的反斜杠- 导致这个反斜杠在 Bash 解析时跑到了单引号字符串的外面
- 从而产生了一个未被转义的单引号,打破了整个参数的边界
- 让我们可以注入任意的 Nmap 命令参数
既然知道最终还是会等于nmap的参数,想到nmap的几个参数:
-iL <file>:从文件读取扫描目标(本题核心,用于读取任意文件)-oN/-oG/-oX <file>:将扫描结果写入文件(用于写 webshell 或保存结果)--script=exec:执行 Nmap 脚本(用于执行系统命令)
第三个想来没什么用,毕竟我们只需要读取文件扫描里面的内容,并且把它输入到另一个文件就行了。
所以我们得出一个payload:
' -iL /flag -o result.txt '这里有个特别重要的细节!!!这个细节出错整个payload用不了,是时候填坑了——
最左和最右两个单引号的各两旁都有个空格,这与Bash绝对不能打破的一个铁律有关:
只有被空格分隔开的东西,才会被当作不同的参数。
把单引号比作门,如果没有空格,即使你把门打开了,这些参数还是会和门粘在一起,变成一个东西。只有加了空格,参数才会和门分开,变成两个不同的东西。
也就是:
- 开头:
'(单引号 + 空格) - 结尾:
'(空格 + 单引号)
把payload输入进去:
看着是不是没什么区别?别慌,因为我们把内容输入到了result.txt里面,所以我们直接查看这个文件就好了。
一顿分析后终于看到flag了。
最后分析一下这个输出作为结尾......
因为flag不是一个有效的域名或 IP 地址,所以 Nmap 会抛出一个错误:
Failed to resolve "flag{}".而 Nmap 有一个特性:所有的错误信息和扫描结果,都会同时输出到两个地方:
- 标准输出(stdout):会被 PHP 的
system()函数直接打印到网页上 - 指定的输出文件:也就是我们用
-o result.txt指定的文件
