23

I'm a programmer working on an application where the only choice/vs/deadline was to implement symmetric encryption on url parameter values. The data is insensitive in nature, but we needed to prevent sales agents from peeking on each other's leads. (Keys are generated on session creation and are cryptographically strong.) Sessions are expected to end frequently.

The role hierarchy was Manager--> Supervisor--> Agents. The data structures don't currently account for these roles in a way to strictly enforce who can see what. Getting this information from the database was NOT anywhere close to straightforward. (Recursive Database.)

I know that this technique is way down on the list as a defence against parameter manipulation. What would have been a better technique?

Constraints:
Role-based checking is not an option.

[Additional information] The urls built and sent to the client before I made any changes looked like:

https://www.example.com/agent/?producerId=12345

The specific threat surface here is parameter manipulation against ?agentId=12345. Agent ids are assigned uniquely to each agent. So if Agent A wants to look at Agent B's stats, he could have entered agentId=22222 in order to look at that agent's quotes and current sales statistics.

Again, Role-Based checking was not an option for me: I was unable to make changes to the database OR the persistence tier.

My solution was to use a session-created encryption key (using Java's KeyGenerator class) and encrypting the outbound urls sent to the client. So now, the url looks like:

https://www.example.com/agent/?producerId=<ciphertext>

Now, if someone tries agentId=22222, the server will decrypt what it thinks is ciphertext and will ultimately create an invalid character sequence.

(This leaves open the possibility that an existing agentId could be found, but quite unlikely that it would be relevant to the person performing the attack.

I will stress that this question isn't about optimal security (which would be role-based checking to ensure resource access) and about trying to squeeze some security in a grey area.

The parameter encryption solution here was recommended to me by one of our security guys. I got one takeaway I hadn't considered on this solution--broken urls--and will be using that as well as the maintenance issue created by this solution to argue for the time to enforce the access rules in a less stopgap fashion.

5
  • 5
    Is there a reason you're using a home-brew protocol to encrypt the traffic, rather than SSL?
    – Polynomial
    Commented Jul 16, 2012 at 15:12
  • 5
    Not a protocol. We're using SSL, but I'm worried about parameter manipulation from the client, not the points between client/server.
    – avgvstvs
    Commented Jul 16, 2012 at 21:24
  • 3
    Something sounds very wrong when you say RBAC is not an option.
    – Luc
    Commented Jul 17, 2014 at 22:22
  • 2
    @Luc Its mostly academic at 2yrs out, but it was really time-constraint. The web portion of that app was new and was built without any concept of RBAC. It was designed as an ad-hoc extension, not a fully-fledged webapp like it needed to be. The architect who was directing me made this design decision in order to meet the deadline.
    – avgvstvs
    Commented Jul 18, 2014 at 16:03
  • 2
    Security was consulted on this as well, and they signed off on this direction, even though I didn't like it.
    – avgvstvs
    Commented Jul 18, 2014 at 16:05

6 Answers 6

17

Good question! Thanks for elaborating on the threat you are trying to defend against. I have edited my answer accordingly.

Summary. Your primary defense should be access control. You need to limit which users can view which pages. Details below.

Access control in web applications. What you need to do is check that the user is authorized to access the data you're going to show on a page, before allowing them to see that data. This basically comes down to access control: you want controls that limit which users can view which data, based upon some authorization policy.

It sounds like you have a sequence of pages, one for each agent:

http://www.example.com/agent/?producerId=12345
http://www.example.com/agent/?producerId=12346
http://www.example.com/agent/?producerId=12347
...

where the producerIds (agentIds) are potentially guessable or predictable. You want to ensure that agent 12345 can view http://www.example.com/agent/?producerId=12345 but not any of the other pages. OK.

This is a bog-standard situation, and the bog-standard defense is: access control.

To implement access control, you code the web application so that each page checks whether the user is authorized to view that page before allowing the user to view that page. For instance, for the page listed above, the logic implementing that page would check the identity of the currently-logged in user. If the id of the logged-in user matches the producerId of the page parameter, then you show them the information. If the id does not match, you do not show them the information: if it is some other user, you show them an error page (with information about how to get access), or if the user has not logged in yet, you redirect them to a login page.

This won't break bookmarks. It does not require changes to the database, changes to the persistence layer, or role-based access control. It does require you to have a way to look up the identity of the currently logged-in user and associate that with their provider ID. Also, if you want to allow manager and supervisors to see the data for all other agents, then you need a way to look up the currently logged-in user and determine whether they are a manager or supervisor or not. If you want to allow only the agent's manager/supervisor to view their page (not all other managers/supervisors), then you need to have a way to determine the manager/supervisor of each agent. These are pretty basic, minimal requirements; it is hard to see how you could avoid them.

As @symbcbean properly points out, this is a very common error frequently found in web applications. A typical example might be a site that uses some guessable parameter value to identify a resource, and does not adequately authenticate the user. For instance, suppose orders are assigned a sequential order number:

https://www.example.com/show_order.php?id=1234
https://www.example.com/show_order.php?id=1235
https://www.example.com/show_order.php?id=1236
...

and suppose that anyone who knows the URL can view the order. That would be bad, because it means that anyone who knows (or guesses) the order number can view the order, even if they are not authorized to do so. This is one of OWASP's Top Ten web application security risks: Insecure Direct Object References. For more information, I recommend reading the resources available on OWASP. OWASP has lots of great resources on web application security.

Other comments. Others have suggested using SSL. While that will not prevent parameter tampering, it is a general good security practice that defends against other kinds of problems. Using SSL is straightforward: just configure your website to use https, instead of http (and ideally, enable HSTS and set the secure bit on all cookies).

Also, it is often better to avoid storing confidential information in URL parameters, all else being equal. You can store the confidential information in session state or in the database.

5
  • I added some extra information in the original question to help guide you where I'm presently at. At the present state of the application, I have no ability to perform role-based checking as what you suggest here. It requires changing a megalith recursive-DB as well as changes throughout the entire persistence layer. To be blunt, despite my protests, despite the SANS training they sent me to get, they don't care because the data isn't sensitive. So I'm more or less fumbling for something serviceable...
    – avgvstvs
    Commented Jul 16, 2012 at 21:56
  • @avgvstvs, I'm puzzled by your statements. Where did role-based checking come from? I did not suggest role-based checking, and nothing I suggested is in any way tied to role-based access control. Not sure where that appeared from. (continued)
    – D.W.
    Commented Jul 17, 2012 at 0:43
  • Moreover, access control checks do not necessarily require changes to the database or the persistence layer. They require only that you (a) know the identity of the currently logged-in user, and (b) know which users should be allowed to access each page. (a) should be true of almost any web application. It appears that (b) is true here: you want only agent 12345 to be able to view the page associated with ?agentid=12345. Seems like it should be straightforward for you to implement access controls as part of the page logic.
    – D.W.
    Commented Jul 17, 2012 at 0:43
  • @avgvstvs, OK, I've edited my answer in light of the additional information you provided. Hopefully this makes more sense now.
    – D.W.
    Commented Jul 17, 2012 at 0:52
  • I apologize, I extend "roles' to mean: A Manager-->CanSee 1:M Supervisor(s)-->CanSee 1:M Agents. (Note the single direction of the arrows.) Theres no field in these data structures to allow me to make these comparisons. The data model is hypernormalized. (Normal queries can require 9 table joins). My decision was based on "what can I get done by release in 4h?" My favored solution is pretty much exactly what you discuss. It's not an option for me until my DBA gets back and can explain this IAA model. Thank you, btw. At least you validated my original idea was right.
    – avgvstvs
    Commented Jul 17, 2012 at 1:50
8

In short: Don't encrypt URL parameters, use a separate look-up.

Also, using HTTPS is basically non-negotiable if you desire any measure of web application security. It's mandatory in 2015. Get comfortable with TLS 1.1+.


What developers want to do

What developers want to do

What developers should do instead

enter image description here

5
  • What even better you could do: avoid numeric, sequential IDs and use something like UUID instead.
    – Narf
    Commented Sep 30, 2015 at 14:10
  • I have no objections to that, Narf. :) Commented Sep 30, 2015 at 16:13
  • At the time the question was asked, https wasn't an issue--it was used. We weren't worried about MiTM, we were worried about a sales agent being able to look at the quotes of people not on his team... classic parameter manipulation, with the constraint of no RBAC. Your solution here would still be vulnerable, as user1 could grab zXTcxOo8QU and look at user's quotes. [Note, database lookups could be used to fix this as well.]
    – avgvstvs
    Commented Oct 1, 2015 at 12:27
  • What we ended up doing is creating a SecureRandom AES key on login, and we encrypted the parameters throughout the session. This prevented URL parameter manipulation, but then introduced the problem that all sales IDs became ephemeral, so you couldn't permalink or bookmark. It was a learning experience, both the question and the project. Few things that happened I would do now.
    – avgvstvs
    Commented Oct 1, 2015 at 12:29
  • 1
    "Your solution here would still be vulnerable" Eh, access controls is out of scope for what's being asked. If you need access controls, implement them. Commented Oct 1, 2015 at 13:04
3

but we needed to prevent sales agents from peeking on each other's leads

This rather implies that the client is a browser - are you sending the key as cleartext at some point?

Polynomial is correct, you should be using SSL. That's not going to solve the problem of users typing in adjacent values to a URL looking something like:

https://www.example.com/show_order.php?id=1234
https://www.example.com/show_order.php?id=1235
https://www.example.com/show_order.php?id=1236
...

It's quite possible to generate an authentication token server-side based on the parameters which must be presented to validate the request. Ideally you would use a message authentication code (MAC) for this, but a hash would work too if you are careful. e.g. in PHP...

 print "<a href='show_order.php?id=" . $id . "&valid=" . md5($id . crypto_key()) . "'>...

Which is validated simply by:

if ($_GET['valid'] != md5($_GET['id'] . crypto_key()) {
   die('not authorized');
}

Here crypto_key() returns a static cryptographic key (generate it by pulling, say, 128 bits from /dev/urandom and storing it in the database).

But you still need to control access to the code which generates the URL.

4
  • 4
    The hash method in the second half of your algorithm is broken and adds no security whatsoever. I know my own session ID (it is in my session cookie) so if I am evil, I can choose any parameter value I want, calculate the appropriate MD5, and ensure that the server-side validation check passes. You probably want a message authentication code (MAC), not a hash function, but the MAC key needs to be stored solely on the server and never sent to clients.
    – D.W.
    Commented Jul 16, 2012 at 18:19
  • 1
    I will stress that SSL is already being used. I'm not concerned with what's between client and server, I'm concerned with what the client will be sending in to snoop.
    – avgvstvs
    Commented Jul 16, 2012 at 21:51
  • 1
    @D.W.: true - using a static value held server side in place of session_id() solves this and resolves the bookmark issue (with the option of using te username as well as the secret static value to prevent portability of bookmarks).
    – symcbean
    Commented Jul 17, 2012 at 16:05
  • @symcbean, Nice solution. I took the liberty of editing your answer to reflect using a static crypto key. Hope that was OK. +1, nice answer.
    – D.W.
    Commented Jul 17, 2012 at 18:16
2

Here is my solution

$id=1234;
$en_id = encrypString( $id);

and I create the url like

https://www.example.com/show_order.php?id=$en_id

the url will look like

https://www.example.com/show_order.php?id=9muEYh4lShFDeCnXqoNpxucs42Fuz5Nexq1IUGWYEffffe88yRbJu

and on the other side I decrypt

$en_id= decryptString($_GET['id']);

the functions for crypt and decrypt are

function encrypString($plaintext) {
         # --- ENCRYPTION ---

        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");//change this

        # show key size use either 16, 24 or 32 byte keys for AES-128, 192
        # and 256 respectively
        $key_size =  strlen($key);
        //echo "Key size: " . $key_size . "\n";


        # create a random IV to use with CBC encoding
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);

        # creates a cipher text compatible with AES (Rijndael block size = 128)
        # to keep the text confidential 
        # only suitable for encoded input that never ends with value 00h
        # (because of default zero padding)
        $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,
                                     $plaintext, MCRYPT_MODE_CBC, $iv);

        # prepend the IV for it to be available for decryption
        $ciphertext = $iv . $ciphertext;

        # encode the resulting cipher text so it can be represented by a string
        $ciphertext_base64 = base64_encode($ciphertext);

        return  rawurlencode($ciphertext_base64);//important rawurlencode for + symbol in url

    }


decryptString($ciphertext_base64) {
        # --- DECRYPTION ---

        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");//change this

        # show key size use either 16, 24 or 32 byte keys for AES-128, 192
        # and 256 respectively
        $key_size =  strlen($key);
        //echo "Key size: " . $key_size . "\n";

        # create a random IV to use with CBC encoding
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);

        $ciphertext_dec = base64_decode($ciphertext_base64);

        # retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
        $iv_dec = substr($ciphertext_dec, 0, $iv_size);

        # retrieves the cipher text (everything except the $iv_size in the front)
        $ciphertext_dec = substr($ciphertext_dec, $iv_size);

        # may remove 00h valued characters from end of plain text
        $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,
                                    $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);

        return rawurldecode($plaintext_dec);
    }
3
  • +1 for using AES-CBC with a urandom-derived IV, -1 for not authenticating your ciphertext. (So: I didn't vote.) Commented Sep 30, 2015 at 13:48
  • Missed declaring 2nd function function decryptString($ciphertext_base64) { Commented Apr 17, 2016 at 19:14
  • But isn't this what the OP actually wanted to avoid and find a "better way"? I agree this would work, but it's a pain to have to decrypt() and call that everytime you need to get the proper URL. Besides, what's stopping a snoop program from doing the decrypting straight from the URL?
    – Fandango68
    Commented Oct 3, 2017 at 6:59
1

To prevent parameter tampering, I've always just sent along a hash with the plain text values.

e.g. take this url you want to secure

https://www.mysite.com/somepage?param1=abc&param2=xyz

On the server, your model would hash all the url values with a secret salt

string salt = "A1B2C3D4...";
string param1 = "abc";
string param2 = "xyz";
string hash = YourFavoriteHashingAlgorithm(param1 + param2 + salt);
// result hash = "Y83YMB38DX83YUHFIEIGKDHSEUG"

then you send this hash along with the other url values

https://www.mysite.com/somepage?param1=abc&param2=xyz&hash=Y83YMB38DX83YUHFIEIGKDHSEUG

Now, when you receive a request to this URL, you again take the parameters you are presented and hash them through the same algorithm. The hash you just generated should match the hash you are being presented, otherwise send them a 400 "Bad Request" result!.

The nice thing is your parameters are still readable by humans, and all your existing validation logic can remain the same.

1
  • In the absence of other controls this does not provide protection against replay attacks.
    – symcbean
    Commented Dec 1, 2017 at 10:16
0

Do not use user input

(because you camnot trust it)

This answer extends the accepted with what looks to me a significant simplification.

Now, from your description and from my best understanding, you have said that you want to prevent Sales Agent A (namely 12345) to peek into Sales Agent B's (namely 54321) data.

Simply, kill the agentId parameter from the query string and get it from the session

The URL becomes https://example.org/show_order.php

Internally, the application must extract the sales agent id from the principal stored within the session. I am excessively rusty in PHP so I will use pseudo code

SELECT * FROM sales where salesman_id = ?1;
[1 = getPrincipalSalesId()]

This query will simply ignore everything that comes from the client. It does not require modifications to the persistence layer. It doesn't even require implementing RBAC (role-based access control), but everything is about that function getPrincipalSalesId.

It's basically the same code you use to generate the URL, but this time you hammer that value into the query, making it implicit.

4
  • The problem with this approach will arise when you two open different orders in two separate browser tabs.
    – cl0ne
    Commented Mar 2, 2022 at 9:20
  • From my perspective, it is intentional that no two browser tabs may display data from different security contexts Commented Mar 2, 2022 at 9:31
  • I meant that if you open two different orders that belong to the same salesman and pass order ID via session variable as well, you'll be in trouble.
    – cl0ne
    Commented Mar 2, 2022 at 10:27
  • I assumed that the only implicit variable is the salesperson id. Your objection is perfectly safe, and the sale ID can be passed as url, but needs no encryption. In this case, however, it must be ANDed in the SQL script with the salesperson ID Commented Mar 2, 2022 at 10:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .