id field is editable through REST PUT request

146 views
Skip to first unread message

Henry Nguyen

unread,
Apr 8, 2014, 7:31:54 PM4/8/14
to web...@googlegroups.com
Our product is using the @request.restful() decorator to specify REST endpoints for our resources. During testing, I noticed that I can specify a PUT request var of "id=x" where x is some new id and the id of that row will change to x. This is even WITH "db.table.id.writable = False." 

The PUT method is defined as follows:

def PUT(table_name, record_id, **vars):
       
return db(db[table_name]._id==record_id).validate_and_update(**vars)

So, for example, on a db with "db.person.id.writable = False", a request to "http://127.0.0.1:8000/appname/default/api/person/1?id=100" will modify the person row with id 1 to be id 100.

This seems like a relatively major problem... if a user were to be clever enough to play around with our UI and figure out the REST calls being made, he/she could potentially mess with all the ids and relationships of the resources, at least for that particular account (and any other resources we've exposed).

Am I missing something? Does "db.person.id.writable = False" only apply to SQLFORMs? Is there some other way to prevent modification of the id field?

Thanks ahead of time for any help.

Massimo Di Pierro

unread,
Apr 9, 2014, 5:03:53 PM4/9/14
to web...@googlegroups.com
> Does "db.person.id.writable = False" only apply to SQLFORMs?

yes. 

Derek

unread,
Apr 11, 2014, 1:36:43 PM4/11/14
to web...@googlegroups.com
That seems like a pretty big hole then especially if IDs are used as foreign keys... ownership doesn't mean anything. I could write an inflammatory comment on a website, change the owner to someone else (via the edit form) and then suddenly that other user is banned...

Massimo Di Pierro

unread,
Apr 12, 2014, 6:01:20 PM4/12/14
to web...@googlegroups.com
That is not a hole.

This code:


def
 PUT(table_name, record_id, **vars):
        
return db(db[table_name]._id==record_id).validate_and_update(**vars)

means:

"allow anybody to put any content in any record of any table". If that is not what you want you should write different code.

Derek

unread,
Apr 16, 2014, 2:06:42 PM4/16/14
to web...@googlegroups.com
You're right, I guess you should store the ID in session state... but wait, this is ReST... part of the url then, and not a parameter. and PUT should not take the record_id.

Henry Nguyen

unread,
Apr 16, 2014, 2:15:42 PM4/16/14
to web...@googlegroups.com
Simply inserting into the tables blindly was the problem, as Massimo pointed out. I've gone ahead and implemented manual checking of the vars:

    def PUT(*args, **vars):

        required_vars
= ['id']
        optional_vars
= ['first_name','last_name']
       
        # Check for required vars
        for var in required_vars:
            if var not in vars.keys():
                raise HTTP(400, 'Missing:  ' + var)

        # Check that vars are only allowed vars
        for key in vars.keys():
            if key not in required_vars and key not in optional_vars:
                raise HTTP(400, 'Invalid: ' + key)

        result
= db(
           
(db.person.id == vars.get('id')) &
           
(db.person.auth_user_id == auth.user.id)
       
).validate_and_update(**vars)

       
return dict(result=result)

I was hoping there'd be an easier way to specify validation constraints for the REST calls, similar to db.table.field.writable = False. Unfortunately, this only applies to the built-in SQLFORMs.

Henry

Niphlod

unread,
Apr 16, 2014, 4:14:21 PM4/16/14
to web...@googlegroups.com
it's not that far-fetch to include a control for writable = False fields.....

def PUT(table_name, record_id, **vars):
    tb = db[table_name]

    cant_update_those = [tb[k] for k in tb.fields if tb[k].writable is False]
    invalid_fields = set(vars) && set(cant_update_those)
    if invalid_fields:
        raise HTTP(400, 'whatever')
   
return db(tb._id==record_id).validate_and_update(**vars)

ask for more details if needed.

Henry Nguyen

unread,
Apr 16, 2014, 4:50:13 PM4/16/14
to web...@googlegroups.com
Nice... thank you, Niphlod... I hadn't even considered checking that field attribute directly like that. 

Henry
Reply all
Reply to author
Forward
0 new messages