The crypt() family of functions suffers from a poor name selection. They actually hash the original password and produce a result that is unique for a given password but that, if something similar to an inverse could exist, would return either a very large or an infinite set of potential passwords. These functions fall within what is called a cryptographically-strong hash (or digest)

Neither the DES-based crypt() nor the MD5-based crypt() use a straight application of the respective digest algorythm. Rather, they involve complex transforms that deliberatelly "lose bits" from the supplied plaintext (ie, the password). This adds additional roadblocks to recovering the original password.

The relative superiority of the MD5-based crypt(), stems from the fact that the MD5-based transformation can be applied to passwords of any length. The two common "flavors" of the DES-based crypt() I'm aware of, have limits in the length of the password.

While it is true that certain "vulnerabilities" have been shown into the MD5 algorythm, it is still far away from being cracked. Note that DES has been considered obsolete since long, yet the passwords protected by DES-based crypt() still need to be cracked using brute force. I would expect the same from the MD5-based one.

Here is where using a salt string works best, as this forces the eventual attacker to scan a much larger key space. The salt, tipically a set of random bytes concatenated to the original password and stored in-the-clear along with the hashed password, increase significantly the key space. Thanks to it, the attacker cannot count on building a large set of hashes whose passwords are known; He would have to calculate all of them just for you.