Apache2+Perl 造成的 CRLF 注入问题
0x00 前言
这本来是在工作中遇到的一个 case,觉得比较有趣,就复现并记录一下。
在线上遇到了一个 CRLF
注入的漏洞,请求 http://example.com/test%0d%0aA%3DB
就会触发。起初没怎么在意,以为是 nginx
配置文件编写不当导致的,但是经过粗略的分析,发现 nginx
配置十分简单,只是普通的 proxy_pass
而已,理论上不会出现 CRLF
漏洞,于是浏览了一下这个仓库中其他的文件,发现存在一个 perl
脚本,初步猜测和这块有关系,经过一番分析后,确实如此。
0x01 环境搭建
因为已经看到了有问题的代码,所以直接复现即可。
先安装 Apache2
和 mod_perl
,并且开启 mod_cgi
。
> apt install apache2 libapache2-mod-perl2
> a2enmod perl
> a2enmod cgi
接着修改默认的配置文件/etc/apache2/sites-enabled/000-default.conf
,还原出有问题的配置:
<VirtualHost *:9999>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ErrorDocument 404 /cgi-bin/missing.pl
</VirtualHost>
关键是 ErrorDocument
这行配置,这就是罪魁祸首。其中 missing.pl
的代码如下:
#!/usr/bin/perl
use CGI;
my $q = CGI->new;
my $new_uri = $ENV{'REDIRECT_URL'};
printf("Location: http://lightless.me?from=%s\n", $new_uri);
printf("Status: 302\n");
print "\n"
如果
perl
脚本无法正常执行,请检查cgi
模块是否开启并且正确配置,在/var/log/apache2/error.log
中可能有错误原因。
测试效果如下:
# 正常请求
☁ cgi-bin curl -v "http://localhost:9999/bbbb"
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9999 (#0)
> GET /bbbb HTTP/1.1
> Host: localhost:9999
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 20 Jul 2021 14:41:30 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Location: http://lightless.me?from=/bbbb&a=b
< Transfer-Encoding: chunked
< Content-Type: text/x-perl
<
# 有问题的请求
☁ cgi-bin curl -v "http://localhost:9999/bbbb%0d%0aSet-Cookie:c%3dd%0d%0afoo:"
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9999 (#0)
> GET /bbbb%0d%0aSet-Cookie:c%3dd%0d%0afoo: HTTP/1.1
> Host: localhost:9999
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 20 Jul 2021 14:41:19 GMT
< Server: Apache/2.4.29 (Ubuntu)
< foo: &a=b
< Set-Cookie: c=d
< Location: http://lightless.me?from=/bbbb
< Transfer-Encoding: chunked
< Content-Type: text/x-perl
<
可以看到已经成功的注入了 Set-Cookie
头。
0x02 分析
我们先看看 ErrorDocument
这个指令是干什么的,根据官方文档给出的解释 What the server will return to the client in case of an error
可以知道,就是用来自定义错误页面用的。
这个指令的格式如下(来自:https://httpd.apache.org/docs/2.4/mod/core.html#errordocument):
ErrorDocument error-code document
其中 error-code
表示要对哪种错误状态码进行处理,而 document
其实就是处理参数,可以是一个简单的字符串,也可以是一个Local URL(当前站点的某个页面)或者 External URL(外部站点),也可以是一个 cgi
脚本。
如果指定是一个 cgi
脚本的时候,那么脚本必须要在标准输出中返回 Status:
头告知客户端 HTTP 状态码
。
...
print "Some Values\n"
printf "Status: %s Condition Intercepted\n", $ENV{"REDIRECT_STATUS"}
那么这些环境变量是从哪里来的呢?
根据文档可知,当触发 ErrorDocument
指令的时候,httpd
会额外的设置一些环境变量给到 ErrorDocument
的上下文中,以便于 ErrorDocument
可以使用这些值。这些环境变量都是来自于原始的请求头的,并且会给变量名添加 REDIRECT_
前缀。例如原始请求头中的 USER_AGENT
,会变成 REDIRECT_USER_AGENT
。
文档中特别提到的是,REDIRECT_URL
,REDIRECT_STATUS
,REDIRECT_QUERY_STRING
三个变量是一定会设置的,其余的变量根据原始请求实际情况进行设置,例如:
- REDIRECT_HTTP_ACCEPT
- REDIRECT_PATH
- REDIRECT_REMOTE_ADDR
- 等等
而我们的 perl
脚本中,取了环境变量中 REDIRECT_URL
的值,并且拼到了 Location:
头中,由于 REDIRECT_URL
是已经解码过的,%0d%0a
已经被解码成了 \r\n
,于是造成了 CRLF
注入。
而修复的话,方法也比较简单,直接删除 REDIRECT_URL
中的 \r\n
换行符即可。
my $new_uri = $ENV{'REDIRECT_URL'};
$new_uri =~ s/[\n\r]//g;
test
Crawle
Crawle
Crawle
Crawle
Crawle
Crawle
Craw
Craw
Craw
zsyqyeuyrcxtxdhchjue
Craw
Craw
Craw