可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a mobile app (currently IOS and soon Android) which talks to a web service. There is no login and the data is not private. Basically, the app POSTs a marker (lon, lat) and GETs the nearest 25 markers to display on a map.
It's a very trivial app and I cannot imagine anyone putting great effort into abusing the web service. However, I can see there is fun for someone in POSTing many markers. What most concerns me is someone running a script that pushes many requests (using expensive bandwidth and making nonsense of my app data).
I am slowly reaching the conclusion this cannot be secure. The best answer is "do not do this". Do not provide a web service without authentication. Not many services are so open. Google's You Tube API is open but most are not. Unfortunately, I have no choice. So after days of looking at this here's my thinking. Be aware I am very far from a security expert and I am confident my approach could be improved upon. But it might point you in the right direction. Hopefully, someone more experienced might chime in and correct/improve upon this. I found this article and comments particularly helpful.
Message Level Security
I will secure the msgs with a hash encryption. The clients and web service all retain a copy of a shared secret which is used as a salt to create a hash from the URL and all the POST arguments. The hash is passed as an additional argument and the hash is rebuilt and compared at the other end (using the shared key as a salt). This is pretty good until you understand that any mobile client code can be reverse engineered in minutes. At which point this line of defense is utterly useless.
Client Measures
The client includes rate limiting of messages as a measure to restrict the number of messages sent by honest users. Yet again this is useless against an attacker who jailbreaks the mobile device.
Server Side Security
So the server side must have as much additional security measures as possible, to stand alone on the assumption that your client (and shared secret) is compromised. Here is what I have:
One msg arg is a UTC time which is used to limit replay attacks. This should prevent an attacker from firing the same msg at the server repeatedly.
The server performs rate limiting by IP. Yes, IPs are easily spoofed and proxy switching is childs play but everything helps when you have so little.
Of course, the server strictly validates all arguments, uses parametised queries and doesn't return exceptions.
Transport Level Security
Unfortunately, I am fairly confident that issuing individual client SSL certs is not possible without a registration process. And because I am using the msg hash check (and my data is not private) I am not entirely sure what SSL brings to the table. However, I will probably use SSL (with one app wide cert) because it adds another level of security that is easily and cheaply deployed (albeit at a cost of additional connection time for every msg).
The Gaping Great Big Hole In My Approach
I am warned that should the app become popular that someone will compromise the shared secret on the client. Just because they can and they will probably post it on the internet. So really it all comes down to the server side. Unfortunately, I have no way to identify and block an attacker. This I would dearly love.
A Final Plea
After days of research this is all I have. But I want more. I would particularly appreciate any ideas to beef up the server side. So, I have put all my SO points up as a bounty. Yes sir, all 97 points!
回答1:
Actually in your particular case, since it is currently an iOS only app, there is a solution.
After the user downloads and runs the app for the first time, the app hits a /access_token/create
API that comes up with a GUID and relays it back to the Application via Apple's Push Notifications.
App stores this access_token, and uses it on all subsequent requests. Your actual API's can rate limit on the basis of the access_token.
Basically, you let Apple do all the hard work of ensuring that the initial request came from an actual iOS device.
Extending this to Desktop clients is possible, but somewhat ruins the UX. Just change step 1 to allow /access_token/create
to accept arbitrary requests, and if the request is not from a iOS device, then force the user to verify their email address/solve a captcha etc before issuing them an access_token.
Android devices (not really familiar with them) may have a similar Push Notification mechanism, in which case you can use that, or may not have a Push Notification mechanism, in which case you can subject your Android users to the inconvenience listed above.
回答2:
I've heard about this idea once, when talking about finding a global solution to SPAM problem: force your client to perform some time-taking computation.
To be precise: find some computational algorithm, that can compute some z
for a pair of x
and y
in a blink of an eye, but it takes some considerable amount of time to compute z
being given only x
. I can not provide actual algorithm but I am sure that there are plenty of them that would much this criteria.
Now the whole procedure should look as follows:
- Upon first client request generate some
session_id
and for this session_id
a pair of x
and y
.
- Provide your client with
session_id
and x
.
- Client can start calculations as soon as it receives data (in some background thread not related to user interactions).
- To request markers, client must provide
session_id
and calculated z
.
- You can quickly verify if client's
z
is all right, for you already have x
and y
that let you easily do it.
- (option 1) For each
session_id
store how much/often it is being requested. The moment you suspect it is being abused - force regenerating x
and y
.
- (option 2) Force new
x
and y
upon each consecutive request for a session_id
.
Choosing between 6 and 7 is actually tweaking that depends on the complexity of algorithm vs. expected 'fair' use of marker database. If your estimates are good - the evil client should never obtain too much data or overload your server.
Hope it helps.
回答3:
There is nothing really you can do on the client side. You have to give the whole app (including any keys or any other protection mechanism) to your user. If a malicious user wants to play mischief with your web service he will have to do some reverse engineering to get to your web service. You can make this harder, but you cannot prevent this, no matter how hard you try.
I’d just implement some server-side rate limiting (per IP address) and not worry any more about this. This is a battle you cannot win. If someone really wants to hurt your server he could just DDOS it without knowing anything about your web service protocol.
Also, generating an unique key or certificate per user automatically on first connect does not help at all. After an attacker reverse-engineered your protocol he knows how to handle all this and doesn't have to play by your rules. Nothing would stop him from requesting a new key from your server every time he runs into the rate limiting.
The approach Kuba Wyrostek described could work - make the client perform some time-consuming computation you can quickly check before you allow the request to be processed. But this can't take too long or your users will complain about the reduced battery life. Also an attacker will probably use more powerful desktop hardware instead of another iPhone.
And a last point - do you really think this is necessary? You don’t want your users to have to register, so your data or service can’t be too important. So what would anyone have to gain from reverse-engineering your app and flooding your server with requests?
回答4:
I've actually been looking for a reason to implement a few of these ideas. Great question and answers so far.
I agree with @Kuba Wyrostek regarding treating it like a spam problem is part of the solution. Especially if your app will have textual messages (adding a store, service, or message), you may find that a common reason to spam your app would be to advertise something. That would lead to my first recommendation:
1) Treat each message's validity as a percentage from 0% to 100% valid. Develop a process on the server side to with heurestics to mark the message as more or less valid. This will allow you to target some of the additional methods (such as forcing a client to calculate a complex value) to only those requests where it is needed. You can also more easily log and review possible abuse (and more easily clean out that abuse after it is targeted).
Your apps do have a strong advantage over email servers in the spam war, however - you control both sides of the conversation. This situation actually reminds me of two other related situations that you might find helpful: the satellite Pay-TV "wars" and Instant Messenger clone "wars". (Reference Jeff Atwoods post on the Black Sunday hack, for example). Here are a few ideas from those standoffs that might help you get a little ahead of the game of cat and mouse:
2) Require the client to send extra data - as much data about the request as makes sense. On iOS, send the accuracy metrics for the location. On Android, you can actually get raw GPS data such as ephemeris information. You can then (maybe not right away, but later), start checking this data for validity. This forces someone reverse engineering the requests to work that much harder. If they send the GPS satellites in view, for example, you could check that against publicly known data to confirm.
3) Force your adversary onto the mobile device - As @Sven notes, your attacker might use a desktop PC, which means a "computationally expensive" request might become trivial. Don't let them do this (or at least make them work harder). You can, for example, have the client compute some mathematical function (sent by the server), and see, based on the phone model, if it takes the correct number of milliseconds to complete. Or do a small 3D rendering task with data from the server, that relies on hardware clipping behavior. Hash the result and send it back. All of these would be within a range - it is a multitasking OS. But it would help tremendously.
4) Go dynamic on them - Send along bits of algorithm that need to be computed in the client's context. Apple gets a little funny about remote code to be interpreted, but something like sending a bit of javascript that does not render to the user might work. That code could ask all sorts of unique questions (screen resolution, browser version, WebKit quirks) that would be hard to anticipate up front. As they catch up, you can get more creative with these.
5) CAPTCHA - If your heuristics start seeing suspect data, force them to authenticate. If you have a multilingual app, it could be as simple as matching a picture or unicode character to another one. Render it in a way that you can update it later.
Anyhow - a few additional ideas. Good luck!
回答5:
The simplest way to implement rate limiting on the server side is to just use a web server plugin/module. For example, if your service is running on apache, install and configure mod_evasive. This will allow you to rate limit based on IP address, service/URL, etc. Mod Evasive will be more effective than what you can implement on your own.
If you use keys, you need to have some kind of captcha based way for the client to get the keys upon signup. You could have it drop accounts for abusive users. Right, of course one parameter would be the timestamp which would be verified as recent and in the past on the server side. The key would encrypt the entire payload along with the timestamp, and be added as an additional parameter. Storing frequency of requests on a per-key basis ends up requiring some kind of round-robin database, unless you only check recency of last request.
No purely client-side rate limit will make any difference. Someone could discover your API on the web without ever even having seen your client. I doubt that a shared secret would be effective for long.
You will find plenty of web services that don't require a password and are merely rate limited... for example, the twitter API offers many rate-limited unauthenticated API services.
回答6:
Here is another "solution":
- don't waste time on this issue.
because:
- you don't expose a public interface to the world, so you are free to change your webservice interface at any time via update of webservice and an update to your app.
- the app is "very trivial" (as you called it) and probably isn't used much at the moment
- you probably have better things to do right now and are just consuming time
if there are suspicious performance or query spikes go for the least time consuming solution:
- introduce a password (clientid) saved in your app (blocks 95% of these users) this clientid can later be used to identify different clients if other programmers want to legally access your service
- introduce rate limiting (as mentioned above)
this will in 99,99% solve your problems and you can get to work right now and write awesome new features.
回答7:
It is tricky, you don't expect anybody to tamper the data ... so your concern is not about integrity.
And since you do not maintain any list of clients ... there cannot be any concern about Authenticity?
And for all the well know Webservice attacks (like DoS or replay attacks) you get firewalls that can prevent them. So I don't think you need to bother about them much.
But then you you don't want to send plain text data and want to make sure that your download app is what is pushing the data.
If you look at the approaches you are evaluating :
Secured Key : As I understand the server and the App are going to share the same Key and if I am correct all the apps on all the devices will share the same Key. And when the app pushes the data it hashes the Actual feed and sends across Actual feed + the hashed feed. On the server side you would use the Key and hash the actual feed and verify if it is matching with the hashed feed. And in my opinion this solution is mainly addresses the Data integrity aspect which is not a major concern for you. rgt!
(And yes it will probably be easy to reverse engineer.)
In the above approaches server would need to store the Key So if your Key is compromised your whole service will be and it will be difficult to update all the apps with new key.
Or else if the app generates a Key it will have to send the key on the wire along with the message as digest or something(like timestamp + some random number). Not so difficult to break.
Certificate: Even with certificates you get the same security .. but it is difficult to break but easy to steel :). If you are keeping a private Key with device (ofcourse then you will have to maintain a public key at the server). You will have to assign a private key per client and then the server needs to maintain the public key for all private keys assigned. If a private key is compromized only that single app can be red flaged and requested for an update.
So what is left is from an application development perspective you want to avoid fabricated data. For sake of preventing mischief
The the only point to check such things is in the application logic. You will need to cache the last ten(or what even the optimum number is) feeds (comming from same IP)and have some sort of logic verify if there is a flaw.
回答8:
You could use rate limit + client 'soft' registration.
Basically you would generate a device ID that you could store in the user defaults upon the first request.
For every request you track how many request have been sent to the server and limit it server side.
This can be achieved really quickly.
You can also have some kind of shared secret used to sign your request with the generated device id + post/get parameters