Topic: More secure authentication

Posted under e621 Tools and Applications

Currently, logging using the API is very insecure when using proxies as their owners could be logging all requests. Therefore, I propose 2 changes:

Hashing the API Key

Using a changing password hash + username is way more secure than the api key + username.

In order to generate the password hash, you make a request to /user/secure.json?username=USERNAME. The response looks like this:

{
    "identifier": "SOME RANDOM CHARS",
    "to_hash": "SOME OTHER RANDOM CHARS"
}

The identifier is valid for the next 24 hours. Using "to_hash", you can create a sha512 like this

// apiKey ... the e621 api key
// to_hash ... the to_hash from above
// counter ... a counter that increases with every request so the hash changes with every request
sha512(apiKey + to_hash + counter)

When using the api, you now use the identifier and the hash to prove your identity. The server then computes the same hash using the api key, to_hash and the number of requests made using the identifier and compares it with your hash.

After 24 hours, the identifier is deleted on the server.

The proxy owner only knows your username. They can't do things on your behalf as a hash is only valid once and the hash can only be created if you know the api key.

API keys with specific scope

Allow users to create multiple api keys. The user can choose a name and which the endpoints the key can be used for.

Updated

Isn't SSL enforced on API requests? That should theoretically prevent man in the middle attacks like that.

Updated by anonymous

ethereal-wolf said:
Currently, logging using the API is very insecure when using proxies as their owners could be logging all requests.

HTTPS is the recommended way of accessing e621 API, IIRC. Are you saying that the proxy can log the unencrypted version of the document returned when you do that?

Considering your proposal independently of that issue, I'm +1 on the first idea. You did a good job of justifying it.

Indifferent to the second idea. I can see why you'd like it, but question whether the change would make enough practical difference to security that it would justify itself.

Updated by anonymous

This looks sketchy.

First, I don't get the threat model. A major point of TLS is that untrusted proxies can not be "logging all requests." If that is indeed happening, then something is very wrong.

Second, this looks like a crude one-time password algorithm. Do not roll your own crypto.

Updated by anonymous

This would just be very cumbersome. I do however wish that we can get the API key out of the http request url and put it in the authentication headers where it belongs, eg authentication: bearer <api key>.

Updated by anonymous

NotMeNotYou said:
Isn't SSL enforced on API requests? That should theoretically prevent man in the middle attacks like that.

savageorange said:
HTTPS is the recommended way of accessing e621 API, IIRC. Are you saying that the proxy can log the unencrypted version of the document returned when you do that?

Considering your proposal independently of that issue, I'm +1 on the first idea. You did a good job of justifying it.

Indifferent to the second idea. I can see why you'd like it, but question whether the change would make enough practical difference to security that it would justify itself.

Yes, some proxies (http://cors-anywhere.herokuapp.com/) are designed to forward requests, as direct communication isn't possible in a browser context (cors) and I don't want to use a browser extension/addon to disable cors.

Updated by anonymous

A different approach would be special API endpoints that always require an API key but allow cors requests for web apps.

Updated by anonymous

KiraNoot said:
JSONP is supported, should you actually wish to use a browser and avoid CORS and a cors proxy.

https://e621.net/post/index.json?callback=abc13 For example.

I have been using JSONP for getting posts and tags, but https://e621.net/favorite/create.json<params> returns just an empty page and doesn't even show errors (supplying login, id, password_hash and callback).

(From API help page)

DO NOT impersonate a browser user agent, as this will get you blocked.

The base URL is /comment/vote.json. This action requires an AJAX-like call. That is, your request header must contain X-Requested-With: XMLHttpRequest

I can't change the user agent and headers with jsonp.

Updated by anonymous

ethereal-wolf said:
I can't change the user agent and headers with jsonp.

You may not be able to change the User-Agent with JSONP, still, you can always add the user agent using other ways, in JavaScript for example, it would be like this:

Object.defineProperty(navigator, 'userAgent', {
    get: function () { return 'PROJECT_NAME/VERSION (by YOUR_E621_USERNAME)'; }
});

The string between quotes is how e621 tells us to make the agent in the API page.

Mostly every programming language has it's way to change the User-Agent for a request or the application in general.

Updated by anonymous

ethereal-wolf said:
I can't change the user agent and headers with jsonp.

Why? User-Agent and other headers are part of HTTP itself, not part of the JSON or XML api that's layered on top of it. Unless the system you're using is stunningly crippled, it has support for these things.

Updated by anonymous

Alastair_Rayls said:
You may not be able to change the User-Agent with JSONP, still, you can always add the user agent using other ways, in JavaScript for example, it would be like this:

Object.defineProperty(navigator, 'userAgent', {
    get: function () { return 'PROJECT_NAME/VERSION (by YOUR_E621_USERNAME)'; }
});

The string between quotes is how e621 tells us to make the agent in the API page.

Mostly every programming language has it's way to change the User-Agent for a request or the application in general.

savageorange said:
Why? User-Agent and other headers are part of HTTP itself, not part of the JSON or XML api that's layered on top of it. Unless the system you're using is stunningly crippled, it has support for these things.

I probably wasn't very clear about the environment the app runs in. This app I was talking about ( https://ethanl.gitlab.io/#/e621 ) is not running inside Electron, Cordova, or the like, it's running inside a normal browser. This thread is rather old and authentication is working now for me - using a proxy ๐Ÿ˜. It's just not as secure as it should be.

I know that you can change headers for XMLHttpRequests/fetch, but you can't change headers for script sources. This is a great security feature, since these requests can bypass cors.

The main problem is that the X-Requested-With header can't be modified by any script from any domain. It is set by the browser when using XMLHttpRequests/fetch. And since the e621 API doesn't allow cors requests, any endpoint that requires credentials can't be used with JSONP.

// try to run this in the console of this tab and a new tab with another page from a different domain 
fetch("https://e621.net/post/index.json").then(console.log).catch(console.log)

// promise resolves on e621.net
// promise rejects on any other domain

This is the console output:

Access to fetch at 'https://e621.net/post/index.json' from origin 'https://www.google.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
TypeError: Failed to fetch
VM70:1 Cross-Origin Read Blocking (CORB) blocked cross-origin response https://e621.net/post/index.json with MIME type application/json. See https://www.chromestatus.com/feature/5629709824032768 for more details.
(anonymous) @ VM70:1

The simplest solution would be to add Access-Control-Allow-Origin: * to every request to the API ๐Ÿ˜œ. But e621 won't do that.

Updated by anonymous

  • 1