dave_the_m: O_NOFOLLOW is a great start. Since, as a rule, suid/guid only matters on *nix scripts, and in fact this one is designed specifically for *nix (it uses /home/username in it) that'll do nicely. I'll put a warning in the header to ensure that it's only run on systems that correctly implement O_NOFOLLOW; I'm suprised I hadn't seen that flag in my wanderings. Thanks!
Tanktalus: sudo doesn't help; I don't care who's running it, in fact anyone should be able to (that's the point of suid, right? Run as one user when you're really another). Also, writing via a symlink /is/ often a Very Bad Idea; see the example in dave_the_m's reply below. If the user is suid root, then you've appended to rhosts, and suddenly there are people being trusted who shouldn't be.
graff, sk: I know it looks that simple, but bear in mind that processes run "concurrently" (not really, unless you're on a multiprocessor system, but they fake it). So looking at dave_the_m's example, all you need is for the timing to be just right. Since as a rule a malicious attacker doesn't mind running something (probably nice'd) a few thousand times to get an attack to work, testing before and after isn't enough. Google "atomicity" for more info.
So: a general solution? Seems like no. One that will work for this situation? Yes. Suid isn't common on most systems that don't support the O_NOFOLLOW flag (believe it or not, suid exists to a degree on windows - eg. run 'at' as admin, have it open cmd.exe - bam you've escalated to Machine and can overwrite/modify/delete absolutely anything), so hopefully my incessant curiosity won't spend any more of my time on the topic, until it's a neccessary question again.
Thanks for all your replies, I really appreciate them! Oh monks, you have yet again shown this follower a better path. | [reply] |
Then when I run the OP's suid script, there's a good chance that between the test and the open, the symlink may appear; thus I get to create or truncate any file owned by that user. (emphasis added)
But, open( O, ">>foo" ) does not truncate the output file (and dying before writing leaves it unmodified). So why would there still be a problem with the code suggested in my second reply?
| [reply] [d/l] |
Because you're still vulnerable to the race condition, it's just very timing dependent. Here's how the timeline falls out. At first, the file doesn't exist.
unless ( -l "foo" ) { # we get here fine
#*now* the link is created
open( FH, ">>foo" ) or die "foo: $!";
#and now it's removed again
}
die "Link attack detected" if ( -l "foo" );
#too late, the link is gone already, so we didn't die.
Now, it's really, really hard to get the timing to work perfectly for that, but that doesn't mean that it won't happen sometimes. So if you start a program creating and removing the link very quickly, and at the same time run the other program again and again, sooner or later you're going to get unlucky. | [reply] [d/l] |
unless ( -l "foo" ) { # we get here fine
#*now* the link is created
open( FH, ">>foo" ) or die "foo: $!";
#and now it's removed again
}
die "Link attack detected" if ( -l "foo" or ! -e _ );
# we have checked for both existence and "type == symlink" on the same
+ stat call,
# so either it's a link, or it's non-existent, or it's safe to write o
+utput
| [reply] [d/l] |