0x00 前言

这本来是在工作中遇到的一个 case,觉得比较有趣,就复现并记录一下。

在线上遇到了一个 CRLF 注入的漏洞,请求 http://example.com/test%0d%0aA%3DB 就会触发。起初没怎么在意,以为是 nginx 配置文件编写不当导致的,但是经过粗略的分析,发现 nginx 配置十分简单,只是普通的 proxy_pass 而已,理论上不会出现 CRLF 漏洞,于是浏览了一下这个仓库中其他的文件,发现存在一个 perl 脚本,初步猜测和这块有关系,经过一番分析后,确实如此。

0x01 环境搭建

因为已经看到了有问题的代码,所以直接复现即可。

先安装 Apache2mod_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_URLREDIRECT_STATUSREDIRECT_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;