12

I am having problems with Hydra and a JSON payload.

The login request (intercepted with Fiddler), is the following:

POST http://architectureservice.test.com/api/v1/login HTTP/1.1
Host: architectureservice.test.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=utf-8
Referer: http://architectureclient.test.com/
Content-Length: 51
Origin: http://architectureclient.test.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

{"username":"tester","password":"test"}

The Response, in case of a wrong password is actually empty because of the fact that it is a Single Page Application. The server will return a 404 (Not Found) or 405 error code instead. In case the credentials are correct, it will proceed an return a 200 page.

As you can see, the request flows from a client to a service. There is actually no form on the service, it takes in the parameters filled in on the client (this is all on my local machine, adapted the HOSTS file). When the credentials are correct, a cookie will be created (this is the first cookie, there is no session cookie or sth before logging in).

Now, the credentials are passed in JSON format. My Hydra command looks as follows:

hydra -L "users.txt" -P "passwords.txt" -s 80 architectureservice.test.com http-post-form "/api/v1/login:{'username'\:'^USER^','password'\:'^PASS^'}:NOT FOUND"

However, this returns that all passwords are valid. Is it possible to use Hydra with JSON format and a Single page application?


UPDATE Thanks to Iserni's answer, I was able to construct the following command:

hydra -v -V -L "users.txt" -P "passwords.txt" -s 80 architectureservice.test.com http-post-form "/api/v1/login:{\"username\"\:\"^USER^\",\"password\"\:\"^PASS^\"}:changeFirstName:H=Accept: application/json, text/plain, */*:H=Accept-Language: en-US,en;q=0.5:H=Accept-Encoding: gzip, deflate:H=Referer: http\://architectureclient.test.com/:H=Origin: http\://architectureclient.test.com:H=Connection: keep-alive"

NOTE: Please note that it is not necessary to escape colons in the Header values. In fact, this breaks the command, so you must not escape the colons there.

I intercepted this request with Wireshark, and it looks exactly the same as the one made from Firefox, except for the JSON payload. Hydra creates a 'x-www-form-urlencoded' body. If I try encoding it this way using a Firefox request (intercepted with fiddler), I get a 'not found' error. So I indeed need to be able to create a JSON content-type. Is this possible with Hydra?

To clarify things, here's a screenshot of the Wireshark capture: Wireshark screenshot Everything is exactly the same as when a request from Firefox is made, except for the Content-Type header and thus also the body payload (from Firefox it is JSON).


UPDATE: SOLUTION

hydra -v -V -L "users.txt" -P "passwords.txt" -s 80 architectureservice.tester.com http-post-form "/api/v1/login:{\"username\"\:\"^USER^\",\"password\"\:\"^PASS^\"}:S=firstName:H=Accept: application/json, text/plain, */*:H=Accept-Language: en-US,en;q=0.5:H=Accept-Encoding: gzip, deflate:H=Referer: http\://architectureclient.tester.com/:H=Origin: http\://architectureclient.tester.com:H=Connection: keep-alive"

S=: I used this because in case of a failure, we get an empty response. The S= can be used to tell Hydra what comes back in case of a valid response. (We send back the firstName in case of a success)
H=: I noticed that Hydra understands that in a header, there will always be a colon. So you do not need to escape colons in headers. In other places, you do.

The above command works, in case you adapt the source of Hydra as follows (for Hydra 7.6): Around line 327 of hydra-http-form.c:

  if (strcmp(type, "POST") == 0) {
    sprintf(buffer,
            "POST %.600s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Hydra)\r\nContent-Type: application/json\r\nContent-Length: %d\r\n%s%s\r\n%s",
            url, webtarget, (int) strlen(upd3variables), header, cuserheader, upd3variables);
    if (hydra_send(s, buffer, strlen(buffer), 0) < 0) {
      return 1;

As you can see, I took the ugliest solution possible (switch hardcoded header value from "x-www-form-urlencoded" to "json". Iserni (see answer below) suggested a better approach, but I got some syntax errors and decided to just hardcode the json value. Also, the Content-Type is hardcoded in multiple places in the hydra-http-form.c file, please change wherever necessary for your situation.

Now, you can use Hydra to bruteforce json web apps.

Gilles 'SO- stop being evil'
  • 50,912
  • 13
  • 120
  • 179
Michael
  • 5,393
  • 2
  • 32
  • 57

1 Answers1

6

Provided the JSON is okay (see at bottom), the problem is likely that the headers now sent by Hydra are not the same as those of the AJAX login form.

The major sources of header trouble (apart from User-Agent, of course) are usually

  • the Content-Type header
  • the Accept header
  • the Origin header
  • the X-Requested-With header
  • Referer and CSRF checks.
  • Cookies,

Many frameworks, both as debugging aid and as "security", will check XRW header and/or CSRF countermeasures, and several routers will offer a redirect (or a HTTP/1.1 200 OK page) if the content type and accept parameters are not suitable to be a JSON request, which your login should be,

Cookies don't seem to be your problem.

You need first of all to check out all the headers that are necessary, which you can do by using cURL and checking a single login (always the same).

(Another valuable check to run is to intercept the framework's response to a single Hydra request. Just suppose that the 200 page Hydra mistakes as correct login says instead "Error so-and-so in input...").

Once the relevant headers and their values have been found, you have to include them in the Hydra sequence. Typos permitting, something like

"/api/v1/login:{\"username\"\:\"^USER^\",\"password\"\:\"^PASS^\"}:NOT FOUND:H=Origin\: http\://architectureclient.test.com:H=Accept\: application/json, text/plain, */*:H=Content-Type\: application/json;charset=utf-8"

Notice that the sequence you used does not seem to be the same JSON flavor of your HTTP request; you use single quotes, but some decoders (notably PHP's json_decode()) will balk. The POST intercept you quote, instead, uses double quotes.

<?php
    $json=<<<JSON
{'user':'joe'}
JSON;
    // Does not work (PHP 5.6.x)
    print_r(json_decode($json, true));
?>

UPDATE about Content-Type

I have given a check to Hydra's source code. Apparently, Content-Type is hard-coded when method is POST. (Hydra 8.0 code, file hydra-http-form.c):

  if (strcmp(type, "POST") == 0) {
    sprintf(buffer,
            "POST %.600s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Hydra)\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\n%s%s\r\n%s",
            url, webtarget, (int) strlen(upd3variables), header, cuserheader, upd3variables);
    if (hydra_send(s, buffer, strlen(buffer), 0) < 0) {
      return 1;
    }
  } else {
    ...

Solutions. Hmmm. A really ugly and dirty hack:

sed -e 's/Content-Type: application\/x-www-form-urlencoded/X-Would-Type: application\/x-www-form-urlencoded/' < hydra > hydra2

...this will produce a new binary that won't send a Content-Type header but a X-Would-Type one. The two strings being equal length and binaries not being signed or MD5summed, it should work. Now you can add a Content-Type header of your own, and that should pass.

I'm not completely sure about how to send multiple headers with Hydra, though. Commands seem colon separated, and since headers too contain colons...

A better solution

Edit the code so that it reads (near line 298 in my source):

  if (strcmp(type, "POST") == 0) {
    sprintf(buffer,
            "POST http://%s:%d%.600s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Hydra)\r\n%sContent-Length: %d\r\n%s%s\r\n%s",
            webtarget, webport, url, webtarget,

    ((NULL == strstr("Content-Type", cuserheader))
     ? "Content-Type: application/x-www-form-urlencoded\r\n"
     : "")

    (int) strlen(upd3variables), header, cuserheader, upd3variables);
    if (hydra_send(s, buffer, strlen(buffer), 0) < 0) {
      return 1;
    }
  } else {

...so that if you supply a Content-Type of your own, it does not add one itself.

LSerni
  • 22,521
  • 4
  • 51
  • 60
  • 1
    Will this correctly encode characters (ie hex entity rather than percent encoding)? – SilverlightFox May 13 '14 at 17:26
  • Don't think so. Good call; one would probably need to correctly pre-encode the users and passwords file. – LSerni May 13 '14 at 17:45
  • 1
    I decided to go with the ugly approach, I got some syntax error when using the clean one. Anyway, I got it to work thanks to you. – Michael May 15 '14 at 13:09
  • Hello, is there a better way to do this in 2022? I have spaces and special characters in my JSON and I get the optional parameter format errors also after escaping quotes and colons – tuxErrante Aug 10 '22 at 06:36