原文由dumplogin发表在Unix hacking版: https://www.xfocus.net/bbs/index.php?act=ST&f=19&t=37176
Apache mod_ssl ssl_util_uuencode_binary buffer over 分析和调试方法 ver 1.0 (bkbll#cnhonker.net, http://www.cnhonker.net 2004/06/03)
1. 前言 最近有一些linux厂商说要升级apache的公告,具体问题是出在mod_ssl的ssl_util_uuencode_binary函数上, CVE:http://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0488 buftraq: http://www.securityfocus.com/bid/10355 漏洞描述(from CVE): Stack-based buffer overflow in the ssl_util_uuencode_binary function in ssl_util.c for Apache mod_ssl when mod_ssl is
configured to trust the issuing CA, may allow remote attackers to execute arbitrary code via a client certificate with a long subject DN. 当时看到不是默认配置就有的,所以就没怎么看它. 后来看到论坛有人问一些相关信息, 又有邮件来问, 决定研究一下. 调试平台:Redhat Linux 默认安装,apache 2.0.40 [root@mobilelinux httpd]# rpm -qa|grep mod_ssl mod_ssl-2.0.40-8
2. 漏洞成因: 这里:http://lists.netsys.com/pipermail/full-disclosure/2004-May/021610.html有比较详细的描述,我copy过来: +---------------------------------------------------------------------------------+ in ssl_util.c there is: ------------------------------------- void ssl_util_uuencode_binary( unsigned char *szTo, const unsigned char *szFrom, int nLength, BOOL bPad) { const unsigned char *s; int nPad = 0;
for (s = szFrom; nLength > 0; s += 3) { *szTo++ = ssl_util_uuencode_six2pr[s[0] >> 2]; /*PROPOSED PATCH: add "if (--nLegth ==0 ) ..." */ *szTo++ = ssl_util_uuencode_six2pr[(s[0] << 4 | s[1] >> 4) & 0x3f]; if (--nLength == 0) { nPad = 2; break; } *szTo++ = ssl_util_uuencode_six2pr[(s[1] << 2 | s[2] >> 6) & 0x3f]; if (--nLength == 0) { nPad = 1; break; } *szTo++ = ssl_util_uuencode_six2pr[s[2] & 0x3f]; --nLength; } while(bPad && nPad--) *szTo++ = NUL; *szTo = NUL; return; } +-----------------------------------------------------------------------------+ 我们来看一下szTo和szFrom的定义: In ssl_engine_kernel.c:
int ssl_hook_UserCheck(request_rec *r) { SSLConnRec *sslconn = myConnConfig(r->connection); SSLSrvConfigRec *sc = mySrvConfig(r->server); SSLDirConfigRec *dc = myDirConfig(r); char buf1[MAX_STRING_LEN], buf2[MAX_STRING_LEN]; char *clientdn; const char *auth_line, *username, *password; ................... apr_snprintf(buf1, sizeof(buf1), "%s:password", clientdn); ssl_util_uuencode(buf2, buf1, FALSE);
apr_snprintf(buf1, sizeof(buf1), "Basic %s", buf2); ............... }
In ssl_util.c:
void ssl_util_uuencode(char *szTo, const char *szFrom, BOOL bPad) { ssl_util_uuencode_binary((unsigned char *)szTo, (const unsigned char *)szFrom, strlen(szFrom), bPad); }
从调用关系就可以看出来,szTo和szFrom其实就是ssl_hook_UserCheck里面的buf2,buf1 大小为:MAX_STRING_LEN,来看看这个大小: [root@DUMPLOGIN E:\download\linux\httpd-2.0.40]# grep MAX_STRING_LEN include/* -r
include/httpd.h:#define MAX_STRING_LEN HUGE_STRING_LEN include/mpm_common.h:extern char ap_coredump_dir[MAX_STRING_LEN];
[root@DUMPLOGIN E:\download\linux\httpd-2.0.40]#grep HUGE_STRING_LEN include/* -r
include/httpd.h:#define MAX_STRING_LEN HUGE_STRING_LEN include/httpd.h:#define HUGE_STRING_LEN 8192
大小就是0x2000
那么从ssl_util_uuencode_binary很容易看出,szTo可以大概被覆盖(0x2000/3). 我们来看看调用时候内存结构图:
低地址 --> 高地址
|---[buf2]---|---[buf1]---| somedata |pointer | pointer | pointer | saved ebp | saved eip |------ buf2既然可以被覆盖大概0x2000/3字节,那么他能覆盖且只能覆盖到buf1,而且还不能完全覆盖buf1. 当调用ssl_util_uuencode_binary时候,buf2和buf1之间同样没有其他变量可以覆盖,所以想在这里覆盖点什么基本上 不大可能,我们只能希望于调用结束后能利用buf2做其他的事情, 但我们来看: apr_snprintf(buf1, sizeof(buf1), "%s:password", clientdn); ssl_util_uuencode(buf2, buf1, FALSE);
apr_snprintf(buf1, sizeof(buf1), "Basic %s", buf2); ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,"Faking HTTP Basic Auth header: \"Authorization: %s\"", buf1); return DECLINED;
一直到函数返回,buf2除了用做写到buf1的apr_snprintf函数一个参数外,没其他用处. 结论:在x86系统上基本没什么大作用,连让进程崩溃都不大可能.
3. 调试方法 这里就涉及到很多关于httpd.conf配置和ssl配置方面的东西,本来很简单,但有人问起来就写下吧,其实写这个才是 本文章的主要目标. 调试系统:redhat linux 8.0 x86默认安装. (1). 修改服务端配制,主要要点在于修改ssl.conf打开信任CA等相关开关. 在此之前你需要一个ca.pl文件,该文件在各版本openssl tgz包中都有. [root@DUMPLOGIN E:\download\linux]#dir "openssl-0.9.7d\apps\CA.pl" 驱动器 E 中的卷是 DOCUMENT 卷的序列号是 F015-E42A
E:\download\linux\openssl-0.9.7d\apps 的目录
2004-03-17 08:08p 5,392 CA.pl 1 个文件 5,392 字节 0 个目录 2,449,305,600 可用字节 [root@mobilelinux ca]# ls ca.pl [root@mobilelinux ca]# pwd /root/ca [root@mobilelinux ca]# ./ca.pl -newca CA certificate filename (or enter to create)
Making CA certificate ... Using configuration from /usr/share/ssl/openssl.cnf Generating a 1024 bit RSA private key ...................++++++ ....++++++ writing new private key to './demoCA/private/cakey.pem' Enter PEM pass phrase: Verifying password - Enter PEM pass phrase: ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [GB]: State or Province Name (full name) [Berkshire]:guangdong Locality Name (eg, city) [Newbury]:guangzhou Organization Name (eg, company) [My Company Ltd]:www.my.com Organizational Unit Name (eg, section) []:test Common Name (eg, your name or your server's hostname) []:10.10.10.114 Email Address []:bkbll@cnhonker.net [root@mobilelinux ca]# ls ca.pl demoCA [root@mobilelinux ca]# ./ca.pl -newreq Using configuration from /usr/share/ssl/openssl.cnf Generating a 1024 bit RSA private key ..............++++++ ..++++++ writing new private key to 'newreq.pem' Enter PEM pass phrase: Verifying password - Enter PEM pass phrase: ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [GB]: State or Province Name (full name) [Berkshire]:guangdong Locality Name (eg, city) [Newbury]:guangzhou Organization Name (eg, company) [My Company Ltd]:www.my.com Organizational Unit Name (eg, section) []:test Common Name (eg, your name or your server's hostname) []:homelinux Email Address []:bkbll@tom.com
Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: Request (and private key) is in newreq.pem [root@mobilelinux ca]# ./ca.pl -sign Using configuration from /usr/share/ssl/openssl.cnf Enter PEM pass phrase: Check that the request matches the signature Signature ok The Subjects Distinguished Name is as follows countryName :PRINTABLE:'GB' stateOrProvinceName :PRINTABLE:'guangdong' localityName :PRINTABLE:'guangzhou' organizationName :PRINTABLE:'www.my.com' organizationalUnitName:PRINTABLE:'test' commonName :PRINTABLE:'homelinux' emailAddress :IA5STRING:'bkbll@tom.com' Certificate is to be certified until Jun 2 11:17:20 2005 GMT (365 days) Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated Signed certificate is in newcert.pem [root@mobilelinux ca]# ls ca.pl demoCA newcert.pem newreq.pem [root@mobilelinux ca]# openssl rsa < newreq.pem > newkey.pem read RSA key Enter PEM pass phrase: writing RSA key [root@mobilelinux ca]# ls ca.pl demoCA newcert.pem newkey.pem newreq.pem [root@mobilelinux ca]# mv newcert.pem server_cert.pem [root@mobilelinux ca]# mv newkey.pem server_key.pem [root@mobilelinux ca]# mv newreq.pem server_req.pem 然后将拷贝几个文件(demoCA/cacert.pem, server_cert.pem,server_key.pem,server_req.pem) 到随便那个目录,我的是/etc/httpd/conf/ssl/目录. 然后需要修改/etc/httpd/conf.d/ssl.conf文件:
SSLCertificateFile /etc/httpd/conf/ssl/server_cert.pem SSLCertificateKeyFile /etc/httpd/conf/ssl/server_key.pem SSLCACertificatePath /etc/httpd/conf/ssl.crt /* 这个目录本来就有,就指定这个吧 */ SSLCACertificateFile /etc/httpd/conf/ssl/cacert.pem SSLVerifyClient require SSLVerifyDepth 10 SSLOptions +FakeBasicAuth /* 这个一定要 */
修改/etc/httpd/conf/httpd.conf: 加这个:
<Directory /var/www/html/usage> SSLVerifyClient require SSLVerifyDepth 5 SSLCACertificateFile /etc/httpd/conf/ssl/cacert.pem SSLCACertificatePath /etc/httpd/conf/ssl.crt SSLOptions +FakeBasicAuth SSLRequireSSL AuthName "test mod_ssl overflow site" AuthType Basic AuthUserFile /etc/httpd/conf/httpd.passwd /* 这个文件随便指定 */ require valid-user </Directory>
修改后,重新启动apche (2) 触发漏洞的client配置(IE) 在linux上给客户端生成一个证书: [root@mobilelinux ca]# ./ca.pl -newreq Using configuration from /usr/share/ssl/openssl.cnf Generating a 1024 bit RSA private key ......................++++++ ..++++++ writing new private key to 'newreq.pem' Enter PEM pass phrase: Verifying password - Enter PEM pass phrase: ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [GB]: State or Province Name (full name) [Berkshire]:guangdong Locality Name (eg, city) [Newbury]:guangzhou Organization Name (eg, company) [My Company Ltd]:www.my.com Organizational Unit Name (eg, section) []:test Common Name (eg, your name or your server's hostname) []:dumplogin Email Address []:dumplogin@yahoo.com
Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: Request (and private key) is in newreq.pem [root@mobilelinux ca]# ./ca.pl -sign Using configuration from /usr/share/ssl/openssl.cnf Enter PEM pass phrase: Check that the request matches the signature Signature ok The Subjects Distinguished Name is as follows countryName :PRINTABLE:'GB' stateOrProvinceName :PRINTABLE:'guangdong' localityName :PRINTABLE:'guangzhou' organizationName :PRINTABLE:'www.my.com' organizationalUnitName:PRINTABLE:'test' commonName :PRINTABLE:'dumplogin' emailAddress :IA5STRING:'dumplogin@yahoo.com' Certificate is to be certified until Jun 2 11:26:58 2005 GMT (365 days) Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated Signed certificate is in newcert.pem [root@mobilelinux ca]# openssl pkcs12 -export -in newcert.pem -inkey newreq.pem -name "MY CERTIFICATE" -certfile
demoCA/cacert.pem -out mycert.p12 Enter PEM pass phrase: Enter Export Password: Verifying password - Enter Export Password: [root@mobilelinux ca]# ls ca.pl mycert.p12 newreq.pem server_key.pem demoCA newcert.pem server_cert.pem server_req.pem [root@mobilelinux ca]#
将mycert.p12拷贝到windows下,安装该证书. 将newcert.pem拷贝到/etc/httpd/conf/ssl.crt/(SSLCACertificatePath /etc/httpd/conf/ssl.crt) 重命名为.crt扩展名的文件. 将ssl.crt/目录下的Makefile.crt重命名为Makefile 运行make,会自动生成一个link文件. 再将这个crt的内容添加到cacert.pem文件(SSLCACertificateFile /etc/httpd/conf/ssl/cacert.pem) cat newcert.crt >> /etc/httpd/conf/ssl/cacert.pem 重新启动apache.
(3). 调试工具. linux下当然用gdb, windows下我用windbg,以方便server端能attach进程. (4). 开始调试 启动浏览器,配置一下,将 使用ssl2,ssl3,tls1.0 全部勾上. 然后打开windbg, F6 attach到该浏览器, 设置断点:bp ws2_32!send 然后bd 0,暂时禁止断点. 从浏览器上输入linux机器的地址(注意要是刚才在httpd.conf里定义的保护站点,就是usage目录),如果以上步骤正确的话,应该会出来一个选
择证书的对话框: 选择刚才linux颁发的证书,不要点确定哦,回到windbg,按ctrl+break,be 0,启动断点,g,这个时候回到 IE窗口,点确定,ok,IE挂起来了. 回到linux,在root用户下寻找到处理该请求的进程ID: [root@mobilelinux ca]# netstat -antp |grep ":443"|grep ESTABLISHED tcp 0 0 10.10.10.114:443 10.10.10.111:4625 ESTABLISHED 2563/httpd [root@mobilelinux ca]# PID 2563就是我们所要的. [root@mobilelinux ca]# gdb -q -se /usr/sbin/httpd (no debugging symbols found)...(gdb) attach 2563 Attaching to program: /usr/sbin/httpd, process 2563 Reading symbols from /usr/lib/libz.so.1...done. Loaded symbols for /usr/lib/libz.so.1 Reading symbols from /lib/libssl.so.2...done. .............................
Loaded symbols for /usr/lib/liblber.so.2 Reading symbols from /usr/lib/libsasl.so.7...done. Loaded symbols for /usr/lib/libsasl.so.7 Reading symbols from /lib/libnss_nisplus.so.2...done. Loaded symbols for /lib/libnss_nisplus.so.2 0x420d224b in poll () from /lib/i686/libc.so.6 (gdb) 然后输入断点: (gdb) b *ssl_hook_UserCheck Breakpoint 1 at 0x4096a0e0 (gdb) b *ssl_util_uuencode Breakpoint 2 at 0x40975cc0 (gdb) b *ssl_util_uuencode_binary Breakpoint 3 at 0x40975d10 这个时候就已经不需要windbg设置的断点了,bd 0,g 再回到刚才的linux窗口: (gdb) c Continuing. [Switching to Thread 8192 (LWP 3331)]
Breakpoint 1, 0x4096a0e0 in ssl_hook_UserCheck () from /etc/httpd/modules/mod_ssl.so (gdb) c Continuing.
Breakpoint 2, 0x40975cc0 in ssl_util_uuencode () from /etc/httpd/modules/mod_ssl.so (gdb) x/4wx $esp 0xbfffb88c: 0x4096a229 0xbfffb8d0 0xbfffd8d0 0x00000000 (gdb) x/bs 0xbfffd8d0 0xbfffd8d0: "/C=GB/ST=guangdong/L=guangzhou/O=www.my.com/OU=test/CN=10.10.10.114/Email=dumplogin@yahoo.com:password" (gdb) x/bx 0xbfffd8d0 0xbfffd8d0: 0x2f (gdb) c Continuing.
Breakpoint 3, 0x40975d10 in ssl_util_uuencode_binary () from /etc/httpd/modules/mod_ssl.so (gdb)
剩下的事情就不需要我多说了,enjoy it :)
4. 参考: [1]. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0488 [2]. http://www.modssl.org/docs/2.8/ssl_howto.html [3]. http://www.drh-consultancy.demon.co.uk/pkcs12faq.html [4]. http://www.redhat.com/docs/manuals/stronghold/Stronghold-3.0-Manual/admin-guide/chapter2.fm.html [5]. http://www.freebsddiary.org/openssl-client-authentication.php
|