Después de tanto "este se vota a sí mismo" y "estos votan con 50 cuentas cada uno", decidí comprobar lo fácil que sería reventar el concurso. No se trata de demostrar que soy el mejor juáquer que haya existido; en realidad si alguien sabe un poquillo y ve lo que he hecho, probablemente le entre la risa. Pero sí, me gusta que los que saben aún menos (todos somos estúpidos en algo) me digan de vez en cuando lo bueno que soy.
En el proceso de registro y votación, la única identificación que se da es una cuenta de correo, dato que se protege con una contraseña. Obviamente, cualquiera puede registrar varias cuentas y votar una vez con cada una. La única "dificultad" de esto es que cuando se vota se acepta una cookie y en los votos siguientes ya se entra con las credenciales anteriores. Esto tiene fácil solución con borrar esa cookie. Como, además, es una cookie "de sesión", basta con cerrar el navegador para que desaparezca.
Segundo problema: la creación de varias cuentas de correo. No todo el mundo administra su propio servidor de correo, y abrir una cuenta en algunos de los que hay por ahí gratuitos parece más complicado que conseguir un préstamo bancario estando desempleado: nombre de usuario (que no haya cogido otro antes), contraseña, pregunta por si se te olvida la contraseña, dirección de correo alternativa para enviarte la contraseña si se te olvida, adivina cuáles son las letras reviradas de esta imagen, fecha de nacimiento, estudios, salario, gustos personales, color de la ropa interior, suscripciones a esto y a lo otro... Que levanten la mano los que conocieron internet antes del boom comercial (cuando todavía podía uno ver en un día todos los sitios web nuevos que habían aparecido la semana anterior) y lo echen de menos. Lo suponía...
A lo que vamos. Hay sitios en los que te ponen las cosas más fáciles. Mejor aún, hay sitios que dan cuentas antispam de usar y tirar, incluso cuentas públicas, sin contraseña ni registro, que se crean automáticamente con la llegada de un mensaje y desaparecen al cabo de un tiempo. Dada la utilidad pública de esos sitios, no se darán sus nombres. Tampoco es que sean difíciles de encontrar, pero no me gustaría que empezaran a filtrarse.
Ya podemos registrar cuentas sin preocupaciones, pero el proceso de votación sigue sin ser "escalable": requiere nuestra atención, hay que introducir datos que cambian con cada voto, borrar la cookie para votar otra vez... Para unas pocas cuentas vale, pero pasando de la decena resultaría pesado, aburrido y tendente a fallos.
¿No votamos por ordenador? Automaticemos. Todo el proceso se limita a rellenar formularios por web. Veamos qué hace falta:
No he incluido javascript en la lista porque sólo se usa para aplicar estilos, comprobar que se han rellenado los formularios adecuadamente y enviar un montón de información a un "proveedor de estadísticas".
Las tres primeras cosas pueden hacerse con wget. La imagen, supuestamente, es para evitar envíos automatizados, pero es demasiado sencilla: una pequeña
modificación y gocr la lee con una fiabilidad cercana al 100%.
Señores de 20minutos (y otros que utilizan estos chismes), apunten estas cosillas si quieren volver a organizar un lío de estos:
body.onload. O usen <noscript>, que para algo está.
De esa forma, el que tenga javascript disfrutará de la funcionalidad extra y el que no, podrá hacer uso del servicio de todas formas. Las comprobaciones las repiten igualmente en el servidor, ¿no?
¿O se pueden mandar a la mierda servidor y datos con una chorrada parecida a lo que viene más adelante?En los siguientes scripts hay muchas cosas que se pueden cambiar. Para empezar, lo marcado en rojo (y puede que algo más) hay que cambiarlo según el servidor de correo usado, y hay pausas, generación de claves... El proceso intenta copiar lo que haría una persona al votar; es probable que haya formas mucho más sencillas de hacerlo, sin ni siquiera leer la imagen. Aún así, hay detalles que permitirían saber que se está votando con estos scripts, pero como funcionan, así quedan. Se ha usado cygwin (bash, wget, grep, sed, tr...) y gocr, todo libre. Con pocas modificaciones (o ninguna) se pueden usar en sistemas *NIX o con otro OCR.
Otra cosa: yo soy de los que no cortan las líneas, por largas que sean. Así que los cortes que se ven aquí no están en mis scripts y sólo los he puesto para que no se desparrame el asunto. Puede que hayan caído en mal sitio.
############################################################################
## getcode
############################################################################
doneok=0
for gamma in 4 "0.1 -negate" "0.2 -negate" 9; do
convert -trim -gamma ${gamma} -resize 200% "$1" captcha.pnm
code=`gocr -C 0123456789lIS captcha.pnm | tr lIS 115`
if ! echo "$code" | grep -q '[^0-9]'; then
doneok=1
break;
fi
done
if [ $doneok -eq 0 ]; then
touch badimage
cp "$1" badimage.${1##*.}
cygstart .
read code
fi
echo $code
############################################################################
Si no lo encuentra, pregunta, de forma que la salida de este script puede usarse en los otros.
Fuera de cygwin probablemente display "$1" sea mejor en el caso de error (con cygwin también, si se tiene el servidor X funcionando).
Es bastante eficaz, aunque parece que tiene problemas en reconocer dos cuatros seguidos en algunas circunstancias (se tocan y no los separa).
En Diciembre añadieron una línea a la imagen del mismo color del texto, con lo que el script anterior ya no reconocía ni una. Así que hubo que hacer cambios.
############################################################################
## getcode - diciembre
############################################################################
declare -i b w i n
declare -a gamma
while [ -n "$1" ]; do
convert -monochrome "$1" info.pnm
histogram=`identify -verbose info.pnm | grep '[0-9][0-9]: '`
b=`echo "$histogram" | sed -n '/ 0,/s/[^0-9]*\([0-9]*\):.*/\1/p'`
w=`echo "$histogram" | sed -n '/255,/s/[^0-9]*\([0-9]*\):.*/\1/p'`
if [ $b -gt $w ]; then
gamma=(2.5 2.7)
convert -negate "$1" captchatidy.png
else
gamma=(2 2.5 3)
convert "$1" captchatidy.png
fi
n=${#gamma[*]}
found="0"
convert -draw 'image over 0,1 0,0 captchatidy.png' captchatidy.png \
captchamoved.png
convert -fx 'max(u,v)' captchatidy.png captchamoved.png captchaclean.png
for (( i=0; i<n; i++ )); do
convert -trim -gamma ${gamma[i]} -resize 200% captchaclean.png \
captcha.pnm
code=`gocr -C 0123456789IlS captcha.pnm | tr IlS 115`
if ! echo "$code" | grep -q '[^0-9]'; then
found="1"
break
fi
done
if [ "$found" == "0" ]; then
if [ $b -gt $w ]; then
gamma=(8 5)
else
gamma=(8)
fi
n=${#gamma[*]}
for (( i=0; i<n; i++ )); do
convert -trim -sharpen 0 -gamma ${gamma[i]} -resize 200% \
captchaclean.png captcha.pnm
code=`gocr -C 0123456789IlS captcha.pnm | tr IlS 115`
if ! echo "$code" | grep -q '[^0-9]'; then
found="1"
break
fi
done
fi
if [ "$found" == "0" ]; then
touch badimage
cp "$1" badimage.${1##*.}
cygstart .
read code
fi
echo $code
shift
done
############################################################################
Falla más que el anterior y, según por dónde pase la raya, puede confundir la lectura. Aún así, creo que el porcentaje de acierto sigue siendo bueno.
############################################################################ ## makeaccount ############################################################################ AGENT='Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.12) Gecko/20050922 Firefox/1.0.7' SERVERS=(example.com mailer.example.com fakemailer.example.com) function request { OUTPUT="$1" URL="$2" COOKIES="$3" if [ -n "$4" ]; then REFERER="--referer $4" else REFERER="" fi shift 4 wget -q -O ${OUTPUT} --load-cookies ${COOKIES} --save-cookies ${COOKIES} \ --keep-session-cookies --header='Accept: text/xml,text/html,text/plain;\ q=0.8,image/png,*/*;q=0.5' --header='Accept-Language: es' ${REFERER} \ --user-agent="${AGENT}" "$@" ${URL} } function absoluteurl { if [ "${1:0:5}" != "http:" ]; then if [ "${1:0:1}" == "/" ]; then echo "http://www.20minutos.es""$1" else echo "http://www.20minutos.es/""$1" fi else echo "$1" fi } rm form cookiesreg cookiesmail url=http://www.20minutos.es/premios_20_blogs/votar/462/2/ request form ${url} cookiesreg if grep -q "$var recordandoPass" form; then sleep 1 referer=${url} url=http://www.20minutos.es/registro_popup/premios_20_blogs_votar/462/2/ request form ${url} cookiesreg ${referer} if grep -q "$var enviandoRegistro" form; then referer=${url} url=`sed -n 's/<div class="code"><img src="\([^"]*\)".*/\1/p' form` request captcha.png `absoluteurl ${url}` cookiesreg ${referer} line='registro_operacion=inscripcion_usuario&code_ok=' code=`sed -n '/code_ok/s/.*value="\([^"]*\)".*/\1/p' form` user=$(( 1+${RANDOM}%`wc -l words | cut -f1 -d" "` )) user=`sed -n ${user}p words` pass=`echo ${user} | tr [a-m][n-z] [n-z][a-m]`${RANDOM} user=${user}_${RANDOM} server=$(( ${RANDOM}%${#SERVERS[*]} )) line=${line}`./urlencode ${code}`'&user_nickname='${user}'&user_email\ ='${user}%40${SERVERS[server]}'&user_password_1='${pass}'&user_\ password_2='${pass}'&codigo='`./getcode captcha.png` url=${referer} sleep $(( 4+${RANDOM}%6 )) request form ${url} cookiesreg ${referer} --header='Content-Type: \ application/x-www-form-urlencoded' --post-data="${line}" if grep -q "Gracias por registrarte" form; then sleep 100 url='http://www.example.com/fakemailer/maildir.jsp?email='${user} request form ${url} cookiesmail referer=${url} url='http://www.example.com/fakemailer/showmail.jsp?email='${user}'\ &msgnum='`sed -n '/mailer@20minutos/s/.*msgnum=\([0-9]*\).*/\1/p' form` if [ -n "${url}" ]; then request form ${url} cookiesmail "${referer}" url=`sed -n '/confirmar_registro/s/.*href="\([^"]*\).*/\1/p' form` if [ -n "${url}" ]; then request form ${url} cookiesreg if grep -q "Gracias por darte de alta" form; then echo -n "OK " fi fi fi fi fi fi echo ${user} ${SERVERS[server]} ${pass} ############################################################################
En words se espera una lista de palabras (una palabra por línea) para usar como base del nombre de cuenta.
Tampoco es que sea necesario, podría generarse el nombre de forma totalmente aleatoria.
Para evitar problemas, que las palabras sólo tengan símbolos que no necesiten cambios para usarlos en una url.
Si sólo hay letras [a-z] se está seguro (sí, la ñ y los acentos están fuera de la lista).
Si hiciera falta, amplíese urlencode y úsese para los otros campos en la generación de line.
############################################################################
## urlencode
############################################################################
BAD=(\; : @ \& = $ , \< \> \" { } \| ^ [ ] \` \( \))
GOOD=(3B 3A 40 26 3D 24 2C 3C 3E 22 7B 7D 7C 5E 5B 5D 60 28 29)
c=${1//\%/%25}
c=${c//\#/%23}
c=${c//\?/%3F}
c=${c//\//%2F}
c=${c//\\/%5C}
c=${c//+/%2B}
c=${c// /+}
declare -i i n
n=${#BAD[*]}
for (( i=0; i<n; i++ )); do
c=${c//${BAD[i]}/%${GOOD[i]}}
done
echo "${c}"
############################################################################
En el archivo accounts guardaremos los datos de las cuentas registradas
(el script que ejecuta n veces makeaccount, comprueba su salida y añade las cuentas al archivo, queda como ejercicio para el lector).
En cualquier caso, pueden tenerse varios archivos con distintos datos y usar uno u otro según las circunstancias.
Una cuenta en cada línea con los siguientes campos separados por un tabulador:
agents.
Ya que estamos poniendo pausas y nombres "reales", cada uno podrá tener un navegador distinto, ¿no?Ejemplo:
login_email=nombre%40example.com&login_password=trae353 12 1562/2 frivoblog 462/2 humor login_email=usu34%40example.com&login_password=3865nv 1 462/2 \(humor\)\|\(personal\)
En este caso nombre@example.com (clave trae353, navegador 12) vota al blog 1562 en frivoblog y al 462 en humor. usu34@example.com (clave 3865nv, navegador 1) vota al blog 462 en humor y personal.
############################################################################
## sendvotes
############################################################################
function request {
OUTPUT="$1"
URL="$2"
shift 2
wget -q -O ${OUTPUT} --load-cookies cookies --save-cookies cookies \
--keep-session-cookies --header='Accept: text/xml,text/html,text/plain;\
q=0.8,image/png,*/*;q=0.5' --header='Accept-Language: es' \
--referer=${REFERER} --user-agent="${AGENT}" "$@" ${URL}
}
function panic {
echo
echo " *** PANIC *** "
echo "$1"
echo
echo URL "$2"
echo LOGIN "$3"
echo CATS "$4"
echo
cygstart .
read -p "Press ENTER to continue: " line
echo RUNNING
}
function absoluteurl {
if [ "${1:0:5}" != "http:" ]; then
if [ "${1:0:1}" == "/" ]; then
echo "http://www.20minutos.es""$1"
else
echo "http://www.20minutos.es/""$1"
fi
else
echo "$1"
fi
}
. ./readdb "${1:-accounts}"
declare -i usersleft=users
while [ $usersleft -gt 0 ]; do
rm cookies
echo $usersleft
let i=${RANDOM}%users
while [ -z "${userlogin[i]}" ]; do
let i++
if [ $i -eq $users ]; then
let i=0
fi
done
AGENT="${agents[${agent[i]}]}"
REFERER="http://www.mozilla.org"
declare -i ivote j
let ivote=voteindex[i]
for (( j=0; j<votemany[i]; j++ )); do
iurl="http://www.20minutos.es/premios_20_blogs/votar/"${vote[ivote]}
let ivote++
request form.html ${iurl}
REFERER=${iurl}
if grep -q "$var recordandoPass" form.html; then
sleep $((4+$RANDOM%3))
request form.html ${iurl} --post-data ${userlogin[i]}
fi
declare -i sentform
let sentform=0
while grep -q "$function EnviarVotos" form.html; do
imurl=`sed -n 's/<div class="code"><img src="\([^"]*\)".*/\1/p' \
form.html`
imurl=`absoluteurl ${imurl}`
request captcha.png ${imurl}
BOUNDARY="-----------------------------"${RANDOM}${RANDOM}${RANDOM}
./genform form.html "${vote[ivote]}" captcha.png "${BOUNDARY}" | \
unix2dos > form.dat
iurl=`sed -n '/<form .*id="form_votacion"/s/.*action="\([^"]*\).*/\1/p' \
form.html`
iurl=`absoluteurl ${iurl}`
sleep $((6+$RANDOM%4))
request form.html ${iurl} --header="Content-Type: multipart/form-data; \
boundary=${BOUNDARY}" --post-file form.dat
let sentform++
done
let ivote++
if [ $sentform -eq 0 ]; then
panic "Not a form" ${iurl} ${userlogin[i]} ${vote[ivote-1]}
continue
fi
if ! grep -q "Gracias por tu voto" form.html; then
panic "No acepta el voto" ${iurl} ${userlogin[i]} ${vote[ivote-1]}
continue
fi
sleep $((10+$RANDOM%12))
done
userlogin[i]=""
let usersleft--
sleep $((1+$RANDOM%120))
done
############################################################################
############################################################################
## readdb
############################################################################
declare -a userlogin
declare -ai agent voteindex votemany;
declare -a vote;
declare -i users=0;
declare -a agents
declare -i i
MYIFS="$IFS"
IFS=" "
i=0
{
while read -r agents[i]; do
let i++
done
} < agents
{
while read -r -a voteline; do
userlogin[users]=${voteline[0]}
agent[users]=${voteline[1]}
declare -i ivote nvoteline
ivote=${#vote[*]}
nvoteline=${#voteline[*]}
let voteindex[users]=ivote
let votemany[users]=nvoteline/2-1
for (( i=2; i<nvoteline; i++ )); do
vote[ivote]=${voteline[i]}
let ivote++
done
let users++
done
} < "$1"
IFS="$MYIFS"
############################################################################
Sí, el primer IFS es un tabulador.
############################################################################
## genform
############################################################################
{
sed -n '/type="hidden"/s/.*name="\([^"]*\)" value="\([^"]*\)".*/\1 \2/p' "$1" | \
sed "s/operacion/operacion votacion/"
sed -n '/type="checkbox"/s/.*name="\([^"]*\)" value="\([^"]*\)"[^>]*>\(.*\)<\/
label.*/\1 \2 \3/p' "$1" | sed -n /"$2"'/s/\([^ ]*\) \([^ ]*\) .*/\1 \2/p'
echo "codigo "`./getcode "$3"`
} | {
while read name value; do
echo "--""$4"
echo "Content-Disposition: form-data; name=\""${name}\"
echo
echo ${value}
done
echo "--""$4"--
}
############################################################################