El comienzo del viaje fue plácido pero con nubarrones amenazantes en el horizonte. Como el problema lo veia un tanto complicado para testear con mis conocimientos funcionales, decidí descomponer el problema más allá de lo habitual.
En pocas palabras, lo que hay que hacer es dada una palabra, generar todas las combinaciones según un mapa, los detalles están en http://seguridad-agile.blogspot.com/2011/12/tdsl.html
En php el código es [1], que emite por STDOUT en lugar de acumular, asi que la implementación es un poco menos sencilla de lo que se ve. Ya que lo voy a hacer bien, prescindiré de STDOUT, elevando el nivel de dificultad.
Parte del algoritmo que he implementado consiste en tranformar la palabra provista en un lista con sus posibles combinaciones, por ejemplo "hola" se transforma en:
[ ["h", "H"], ["o","O","0"],["l","L","1"],["a","A","4","@"] ]
El test es
preleet_nothing_test()->
?assertEqual([],transform:preleet("")).
preleet_one_simple_char_test()->
?assertEqual([[";"]],transform:preleet(";")).
preleet_one_complex_char_test()->
?assertEqual([["b","B","8"]],transform:preleet("b")).
preleet_many_mixed_char_test()->
?assertEqual([["a","A","4","@"],[";"],["b","B","8"]],transform:preleet("a;b")).
Mi implementación de esto es [2], pero la pongo al final por si querés probarlo.
Sin tener aún el aplomo de encarar lo más duro y a modo de entrenamiento, me desvié con una función que luego me resultará útil para profiling. Lo que hace es dada la lista anterior, calcular la cantidad de combinaciones.
El test es este y la implementación en [3]:
measure_empty_test()->
?assertEqual(0,transform:measure([])).
measure_one_element_size_one_test()->
?assertEqual(1,transform:measure([["1"]])).
measure_one_element_size_two_test()->
?assertEqual(2,transform:measure([["b","B"]])).
measure_two_elements_size_one_test()->
?assertEqual(1,transform:measure([["b"],["B"]])).
measure_two_elements_size_two_test()->
?assertEqual(4,transform:measure([["b","B"], ["a","A"]])).
measure_arbitrary4_test()->
?assertEqual(4,transform:measure([["b","B"], ["c"],["a","A"]])).
measure_arbitrary8_test()->
?assertEqual(8,transform:measure([["b","B"], ["c"],["a","A","4","@"]])).
Debo confersar que he pecado. Estuve en un vertiginoso remolino de codificación incompleta, tests que se fueron ajustando al código y viceversa sin ningún resultado. De repentete, a las 4:30 me llevé por delante la solución, que es tan groseramente sencilla, una vez que la conocés, claro, que no sé si ponerla. El problema es que estaba trabajando con partes internas de la implementación usando tdd cuando encontré la solución completa. Ops! tengo el resultado antes que el test, pero juro que no fue a propósito. Asi que este es el test y en [4] la implementación.
leet_a_test() ->
?assertEqual(["a","A","4","@"],transform:leet("a")).
leet_aa_test() ->
?assertEqual(["aa","aA","a4","a@","Aa","AA","A4","A@","4a","4A","44","4@","@a","@A","@4","@@"],transform:leet("aa")).
leet_bc_test() ->
?assertEqual(["bc","bC","b(","Bc","BC","B(","8c","8C","8("],transform:leet("bc")).
Lo único que falta es que dado a->A,@,4, tener A->a,@,4 y etc. pero me parece que lo voy a hardcodear en map/1.
[1] Implementación original en php. No entiendo bien lo que hice ni me interesa mucho. Me parece que el primer foreach agrega mayúsculas y "relaciones inversas", o sea si existe "a"-> "A" crea o agrega "A"->"a". El segundo foreach genera el array de permutaciones (sea lo que sea) necesario para el string de entrada. Noto una cierta optimización.
$map = array(
'a'=> array('@','4'),
'e'=> array('3'),
etc...
);
$baseArray = str_split($argv[1],1);
foreach ($baseArray as $char) {
if (!isset($map[$char])) {
$map[$char][]=$char;
}
if (! in_array(strtoupper($char) ,$map[$char] )) {
$map[$char][]=strtoupper($char);
}
if (! in_array($char ,$map[$char] )) {
$map[$char][]=$char;
}
}
foreach ($baseArray as $pos=>$char) {
$permArray[$pos]=$map[$char];
}
emit($permArray,0,'');
function emit(array $permArray, $level, $buffer) {
if ($level >= count($permArray)) {
echo "$buffer\n";
return;
}
foreach ($permArray[$level] as $char) {
emit($permArray, $level + 1, "$buffer$char");
}
}
[2] implementación de preleet/1
preleet([]) -> [];
preleet([Head|Tail]) -> [ find([Head], map()) | preleet(Tail) ].
siendo map/1
map() -> [
{"a",["a","A","4","@"]},
{"b",["b","B","8"]} ,
{"c",["c","C","("]},
y el test de find/2
find_a_in_empty_map_test()->
?assertEqual(["a"],transform:find("a",[])).
find_a_in_map_first_test()->
Map = [ { "a", [ "a", "A", "4", "@"] }, {"b", ["b","B"]} , {"c",["c","C","("]}],
?assertEqual([ "a", "A", "4", "@"],transform:find("a",Map)).
find_a_in_map_last_test()->
Map = [ {"b", ["b","B"]} , {"c",["c","C","("]}, { "a", [ "a", "A", "4", "@"] } ],
?assertEqual([" "],transform:find(" ",Map)).
find_a_in_map_with_only_a_test()->
Map = [ { "a", [ "a", "A", "4", "@"] }],
?assertEqual(["a","A", "4", "@"],transform:find("a", Map)).
find_something_in_map_without_something_test()->
Map = [ {"b", ["b","B"]} , {"c",["c","C","("]}],
?assertEqual([";"],transform:find(";",Map)).
[3] implementación de measure/1
measure([]) -> 0;
measure(What) -> measure(What,1).
measure([],Total) -> Total;
measure([Load|Tail],Total) -> measure(Tail, length(Load) * Total).
[4] implementación de leet/1
leet(Word)->
lists:foldl(
fun (Elem, List) ->
[ string:concat(X,E) || X <- List, E <-Elem ] end,
hd(preleet(Word)), tl(preleet(Word) )).
2012/01/04
2012/01/03
Intermedio
Afortunadamente he podido ligar el aprendizaje de erlang con las dos partes actuales de mi actividad profesional. Además de seguridad debo encargarme a mi pesar de administrar jira, algo totalmente no gratificante para mi. Quizas a otras personas les guste, pero yo lo detesto desde los más profundo de mi ser.
Ese rechazo se ha plasmado en el deseo de automatizar; dejémosle a las máquinas el trabajo sucio. ¿Dije antes "seguridad"? ¿Dije "jira"? ¿Dije "erlang"? ¡Buenísimo!
El otro día me enteré que jira ofrece una api [1] y me dije: Si pudiera mediante una api consultar las relaciones entre usuarios, grupos, roles y proyectos, tendría la posibilidad de detectar cualquier desviación de la configuración, como un cliente que haya sido asignado como administrador de un proyecto.
En esta ocasión, dejé el tdd de lado, es una vergüenza, pero es lo que hay.
La idea es poder consultar los grupos de usuarios y los usuarios con sus roles en cada proyecto y luego cruzarlos para detectar irregularidades
El proceso es muy sencillo:
La librería de erlang que use no soporta proxy con https, lo cual me dificultó al comienzo diagnosticar, pues necesitaba ver el tráfico y como jira usa https estaba afuera. Como logré establecer la comunicación exitosamente, no fue vital el problema. Con wireshark se pudo haber hecho teniendo la clave privada del servidor, lo cual no es posible en este caso.
Las llamadas a getGroup(), getProjectsNoSchemes() o getProjectWithSchemesById() son triviales y funcionan sin sorpresas, una vez que se ha podido parsear la respuesta.
En general es asi:
En este caso, nos quedan los usuarios. Si nos interesara, nos podriamos quedar con el id, name y jsonrpc.
Esos dibujitos <<>> alrededor de los strings significan que en realidad son datos binarios y no debería confersarlo, pero me frenó casi una hora.
El problema fue cuando intenté obtener más datos de roles/usuarios por proyecto. Las firmas[3] de los métodos anteriores son:
RemoteGroup getGroup(java.lang.String token, java.lang.String groupName)
RemoteProject getProjectByKey(java.lang.String token, java.lang.String projectKey)
Pero la firma siguiente es:
RemoteProjectRoleActors getProjectRoleActors(
java.lang.String token,
RemoteProjectRole projectRole,
RemoteProject project
)
Intenté ver via netbeans, que tiene un plugin para jira, el tráfico a ver si había algo que me ayudara, pero no hace las llamadas que necesitaba. Para esto use webscarab y le dije a netbeans que lo usara como proxy. Lo interesante es que netbeans no dijo nada, pese a recibir los certificados incorrectos del proxy en lugar de jira.com. Ya que estaba, subí el bug a netbeans[4].
Entonces, ¿cómo le paso un "RemoteProject"? Consideré hacer el conocido ataque de fuerza bruta de probar con key, "key", id, "id" para proyecto y rol pero tras dos combinaciones me aburrí, pensé en Murphy y me dije: "seguro que la solución correcta es la última" y fui directo a ella:
Project = get_project_by_id({{token,Token},{project_id,"1234"}}),
[ProjectRole|Roles] = get_project_roles({token,Token}),
get_project_role_actors(
{{token,Token},{project_role,ProjectRole},{project,Project}}
).
Más que sorpresa alivio me produjo que funcionara de una. No entiendo a quien se le ocurrió implementarlo asi. Alguien me preguntó si habia probado de sacarle algunos campos, ya que estoy enviando Project y ProyectRole:
{[{<<"name">>,<<"Administrators">>},
{<<"id">>,111111},
{<<"description">>,
<<"A project role that represents administrators in a project">>}]}}.
y le contesté lo que supongo que es el motivo por el cual está hecho asi: "¿funciona? ¡LISTO!".
[1] https://developer.atlassian.com/display/JIRADEV/JIRA+JSON-RPC+Overview
[2] https://github.com/jchris/erlang-json-eep-parser
[3] http://docs.atlassian.com/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html
[4] http://netbeans.org/bugzilla/show_bug.cgi?id=206848
Ese rechazo se ha plasmado en el deseo de automatizar; dejémosle a las máquinas el trabajo sucio. ¿Dije antes "seguridad"? ¿Dije "jira"? ¿Dije "erlang"? ¡Buenísimo!
El otro día me enteré que jira ofrece una api [1] y me dije: Si pudiera mediante una api consultar las relaciones entre usuarios, grupos, roles y proyectos, tendría la posibilidad de detectar cualquier desviación de la configuración, como un cliente que haya sido asignado como administrador de un proyecto.
En esta ocasión, dejé el tdd de lado, es una vergüenza, pero es lo que hay.
La idea es poder consultar los grupos de usuarios y los usuarios con sus roles en cada proyecto y luego cruzarlos para detectar irregularidades
El proceso es muy sencillo:
- Generar un json según la llamada a efectuar [2]
- Llamar a jira
- Parsear el resultado
- Hacer los cruces
La librería de erlang que use no soporta proxy con https, lo cual me dificultó al comienzo diagnosticar, pues necesitaba ver el tráfico y como jira usa https estaba afuera. Como logré establecer la comunicación exitosamente, no fue vital el problema. Con wireshark se pudo haber hecho teniendo la clave privada del servidor, lo cual no es posible en este caso.
Las llamadas a getGroup(), getProjectsNoSchemes() o getProjectWithSchemesById() son triviales y funcionan sin sorpresas, una vez que se ha podido parsear la respuesta.
En general es asi:
Json= make_json_getGroup({"Developers",Token}),
Url = "https://host.com/rpc/json-rpc/jirasoapservice-v2",
{ok, {{HttpVer, Code, Msg}, Headers, Body}} =
http:request(post, { Url, [], "application/json", Json }, [], []),
{[{<<"id">>,_}|[{<<"result">>,{[{<<"name">>,_}| [ { <<"users">>, Users}]]}}|[{<<"jsonrpc">>,_}]]]}=
json_eep:json_to_term(Body),
En este caso, nos quedan los usuarios. Si nos interesara, nos podriamos quedar con el id, name y jsonrpc.
Esos dibujitos <<>> alrededor de los strings significan que en realidad son datos binarios y no debería confersarlo, pero me frenó casi una hora.
El problema fue cuando intenté obtener más datos de roles/usuarios por proyecto. Las firmas[3] de los métodos anteriores son:
RemoteGroup getGroup(java.lang.String token, java.lang.String groupName)
RemoteProject getProjectByKey(java.lang.String token, java.lang.String projectKey)
RemoteProjectRole getProjectRole(java.lang.String token, java.lang.Long id)
Pero la firma siguiente es:
RemoteProjectRoleActors getProjectRoleActors(
java.lang.String token,
RemoteProjectRole projectRole,
RemoteProject project
)
Intenté ver via netbeans, que tiene un plugin para jira, el tráfico a ver si había algo que me ayudara, pero no hace las llamadas que necesitaba. Para esto use webscarab y le dije a netbeans que lo usara como proxy. Lo interesante es que netbeans no dijo nada, pese a recibir los certificados incorrectos del proxy en lugar de jira.com. Ya que estaba, subí el bug a netbeans[4].
Entonces, ¿cómo le paso un "RemoteProject"? Consideré hacer el conocido ataque de fuerza bruta de probar con key, "key", id, "id" para proyecto y rol pero tras dos combinaciones me aburrí, pensé en Murphy y me dije: "seguro que la solución correcta es la última" y fui directo a ella:
Project = get_project_by_id({{token,Token},{project_id,"1234"}}),
[ProjectRole|Roles] = get_project_roles({token,Token}),
get_project_role_actors(
{{token,Token},{project_role,ProjectRole},{project,Project}}
).
Más que sorpresa alivio me produjo que funcionara de una. No entiendo a quien se le ocurrió implementarlo asi. Alguien me preguntó si habia probado de sacarle algunos campos, ya que estoy enviando Project y ProyectRole:
{[{<<"key">>,<<"XXXX">>},
{<<"description">>,null},
{<<"url">>,<<"https://host.com/browse/XXXX">>},
{<<"lead">>,<<"xxxxx.xxxxxxxx">>},
{<<"permissionScheme">>,null},
{<<"notificationScheme">>,null},
{<<"issueSecurityScheme">>,null},
{<<"projectUrl">>,null},
{<<"name">>,<<"Xxxxx">>},
{<<"id">>,<<"111111">>}]}.
{<<"description">>,null},
{<<"url">>,<<"https://host.com/browse/XXXX">>},
{<<"lead">>,<<"xxxxx.xxxxxxxx">>},
{<<"permissionScheme">>,null},
{<<"notificationScheme">>,null},
{<<"issueSecurityScheme">>,null},
{<<"projectUrl">>,null},
{<<"name">>,<<"Xxxxx">>},
{<<"id">>,<<"111111">>}]}.
y
{<<"id">>,111111},
{<<"description">>,
<<"A project role that represents administrators in a project">>}]}}.
y le contesté lo que supongo que es el motivo por el cual está hecho asi: "¿funciona? ¡LISTO!".
[1] https://developer.atlassian.com/display/JIRADEV/JIRA+JSON-RPC+Overview
[2] https://github.com/jchris/erlang-json-eep-parser
[3] http://docs.atlassian.com/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html
[4] http://netbeans.org/bugzilla/show_bug.cgi?id=206848
Suscribirse a:
Entradas (Atom)