1. ๋ฌธ์
https://dreamhack.io/wargame/challenges/70
PATCH-1
์ฃผ์ด์ง ์ฝ๋๋ฅผ ๋ถ์ํ๊ณ , ํด๋น ์ฝ๋์ ์กด์ฌํ๋ ์ทจ์ฝ์ ๋ค์ ํจ์นํด๋ณด์ธ์. ๋ฌธ์ ์ ๋ํ ์์ธํ ์ค๋ช ์ /usage ํ์ด์ง๋ฅผ ํ์ธํ์ฌ ๋ณด์๊ธฐ ๋ฐ๋๋๋ค. ๋ชจ๋ ํจ์น๊ฐ ์๋ฃ๋๋ฉด ํ๋๊ทธ๋ฅผ ํ๋ํ ์ ์
dreamhack.io
2. ํด๊ฒฐ ๊ณผ์
(1) ๋ฌธ์ ํ์ด์ง ์ ์
Usage ํ์ด์ง์ ๋ฌธ์ ์ ๋ํ ์์ธํ ์ค๋ช ์ด ์ ํ์๋ค.
์ฃผ์ด์ง ์ฝ๋ (๋ฌธ์ ํ์ผ)๋ฅผ ์์ ํ์ฌ ์ทจ์ฝ์ ์ ํจ์นํ ๋ค ์ ์ถํ๋ฉด ํ๋๊ทธ๋ฅผ ์ป์ ์ ์๋ค.
์ด๋, ์์ ๊ฐ๋ฅํ ์ฝ๋๋ app,py ํ์ผ ํ๋์ด๋ค.
(2) ์ฝ๋ ๋ถ์
์ ์ฒด ์ฝ๋
#!/usr/bin/python3
from flask import Flask, request, render_template_string, g, session, jsonify
import sqlite3
import os, hashlib
app = Flask(__name__)
app.secret_key = "Th1s_1s_V3ry_secret_key"
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.environ['DATABASE'])
db.row_factory = sqlite3.Row
return db
def query_db(query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return "api-server"
@app.route('/api/me')
def me():
if session.get('uid'):
return jsonify(userid=session['uid'])
return jsonify(userid=None)
@app.route('/api/login', methods=['POST'])
def login():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
ret = query_db(f"SELECT * FROM users where userid='{userid}' and password='{hashlib.sha256(password.encode()).hexdigest()}'" , one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
return jsonify(result="fail")
@app.route('/api/logout')
def logout():
session.pop('uid', None)
return jsonify(result="success")
@app.route('/api/join', methods=['POST'])
def join():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
conn = get_db()
cur = conn.cursor()
cur.execute("Insert into users values(?, ?);", (userid, hashlib.sha256(password.encode()).hexdigest()))
conn.commit()
return jsonify(result="success")
return jsonify(result="error")
@app.route('/api/memo/add', methods=['PUT'])
def memoAdd():
if not session.get('uid'):
return jsonify(result="no login")
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
ret = cur.execute("Insert into memo(userid, title, contents) values(?, ?, ?);", (userid, title, contents))
conn.commit()
return jsonify(result="success", memoidx=ret.lastrowid)
return jsonify(result="error")
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
template = ''' Written by {userid}<h3>{title}</h3>
<pre>{contents}</pre>
'''.format(title=title, userid=userid, contents=contents)
return render_template_string(template)
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
ret = query_db('SELECT * FROM memo where idx=?', [idx,])[0]
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if ret and title and contents:
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
๋จผ์ , DB ๊ด๋ จ ์ ํธ๋ฆฌํฐ๋ค์ด ์คํ๋๋ค.
์ด๋ secret key๊ฐ์ด ํ๋์ฝ๋ฉ ๋์ด์๋๋ฐ, ์ด ๋ถ๋ถ๋ ํจ์น ๋์์ผ ๊ฒ์ด๋ค.
#!/usr/bin/python3
from flask import Flask, request, render_template_string, g, session, jsonify
import sqlite3
import os, hashlib
app = Flask(__name__)
app.secret_key = "Th1s_1s_V3ry_secret_key"
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.environ['DATABASE'])
db.row_factory = sqlite3.Row
return db
def query_db(query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
1. ๊ธฐ๋ณธ ๊ฒฝ๋ก ('/')
"api-server" ๋ฌธ์์ด ๋ฐํ
@app.route('/')
def index():
return "api-server"
2. ์ฌ์ฉ์ ์ ๋ณด ํ์ธ ('/api/me')
ํ์ฌ ์ธ์ ์ 'uid' ๊ฐ์ด ์๋์ง ํ์ธํ์ฌ ๋ก๊ทธ์ธ ์ํ์ธ ๊ฒฝ์ฐ ์ฌ์ฉ์ ID๋ฅผ, ์๋ ๊ฒฝ์ฐ์๋ None์ ๋ฐํํ๋ค.
- {"userid": ์ธ์ ์_์ ์ฅ๋_uid_๊ฐ}
- {"userid": null}
@app.route('/api/me')
def me():
if session.get('uid'):
return jsonify(userid=session['uid'])
return jsonify(userid=None)
3. ๋ก๊ทธ์ธ ('/api/login', POST ๋ฐฉ์)
์ฌ์ฉ์๊ฐ form ์ ์ ๋ ฅํ userid์ password๋ฅผ ๋ฐ์์ users ํ ์ด๋ธ์์ ์ผ์นํ๋ ์ ๋ณด๋ฅผ ์ฐพ๋๋ค.
์ผ์นํ๋ ์ฌ์ฉ์๊ฐ ์๋ ๊ฒฝ์ฐ, ์ธ์ ์ uid๋ฅผ ์ ์ฅํ๊ณ succes ์๋ต์ ๋ฐํํ๊ณ , ์๋ ๊ฒฝ์ฐ fail์ ๋ฐํํ๋ค.
@app.route('/api/login', methods=['POST'])
def login():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
ret = query_db(f"SELECT * FROM users where userid='{userid}' and password='{hashlib.sha256(password.encode()).hexdigest()}'" , one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
return jsonify(result="fail")
4. ๋ก๊ทธ์์ ('/api/logout')
์ธ์
์์ 'uid'๋ฅผ ์ ๊ฑฐํ๊ณ succes ์๋ต์ ๋ฐํํ๋ค,
@app.route('/api/logout')
def logout():
session.pop('uid', None)
return jsonify(result="success")
5. ํ์๊ฐ์
('/api/join', POST ๋ฐฉ์)
์ฌ์ฉ์๊ฐ ์ ์ถํ userid์ password๋ฅผ ๋ฐ๊ณ , ๋น๋ฐ๋ฒํธ๋ฅผ SHA-256์ผ๋ก ํด์ํ์ฌ ์ฌ์ฉ์ ํ
์ด๋ธ์ ์ ์ฅํ๋ค.
์ฑ๊ณตํ๋ฉด success, ์คํจํ๋ฉด error ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
@app.route('/api/join', methods=['POST'])
def join():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
conn = get_db()
cur = conn.cursor()
cur.execute("Insert into users values(?, ?);", (userid, hashlib.sha256(password.encode()).hexdigest()))
conn.commit()
return jsonify(result="success")
return jsonify(result="error")
6. ๋ฉ๋ชจ ์ถ๊ฐ ('/api/memo/add', PUT ๋ฐฉ์)
- ๋ก๊ทธ์ธ ์ํ ํ์ธ: ์ธ์ ์ UID๋ฅผ ํ์ธํ์ฌ ๋ก๊ทธ์ธ๋์ง ์์์ผ๋ฉด "no login" ๊ฒฐ๊ณผ ๋ฐํ
- ์ธ์ ์์ ํ์ฌ ์ฌ์ฉ์ ID๋ฅผ ๊ฐ์ ธ์, ์ฌ์ฉ์๊ฐ ์ ์ถํ title๊ณผ contents๋ฅผ ๋ฐ๋๋ค.
- title๊ณผ contents๊ฐ ๋ชจ๋ ์กด์ฌํ๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ Insert๋ฌธ์ ์ด์ฉํด์ ๋ฉ๋ชจ๋ฅผ ์ถ๊ฐํ๊ณ ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ๋ฉ๋ชจ ID(lastrowid)์ ํจ๊ป success ์๋ต์ ๋ฐํํ๋ค.
app.route('/api/memo/add', methods=['PUT'])
def memoAdd():
if not session.get('uid'):
return jsonify(result="no login")
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
ret = cur.execute("Insert into memo(userid, title, contents) values(?, ?, ?);", (userid, title, contents))
conn.commit()
return jsonify(result="success", memoidx=ret.lastrowid)
return jsonify(result="error")
7. ๋ฉ๋ชจ ์กฐํ ('/api/memo/<idx>', GET ๋ฐฉ์)
URL ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ idx๋ฅผ ์ฌ์ฉํ์ฌ ํด๋น ๋ฉ๋ชจ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฒ์ํ๋ค.
์ด๋ mode ํ๋ผ๋ฏธํฐ์ ๋ฐ๋ผ ์๋ต ํ์์ด ๋ฌ๋ผ์ง๋ค. // ์์: /api/memo/5?mode=html
- 'html' ๋ชจ๋: ํ ํ๋ฆฟ ๋ฌธ์์ด์ ๋ ๋๋งํ์ฌ HTML ํ์์ผ๋ก ๋ฉ๋ชจ ๋ด์ฉ์ ๋ฐํ
- ๊ทธ ์ธ: ๋ฉ๋ชจ ๋ฐ์ดํฐ๋ฅผ JSON ํ์์ผ๋ก ๋ฐํ
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
template = ''' Written by {userid}<h3>{title}</h3>
<pre>{contents}</pre>
'''.format(title=title, userid=userid, contents=contents)
return render_template_string(template)
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
8. ๋ฉ๋ชจ ์
๋ฐ์ดํธ ('/api/memo/int:idx', PUT ๋ฐฉ์)
- ๋ก๊ทธ์ธ ์ํ ํ์ธ: ๋ก๊ทธ์ธ๋์ง ์์์ผ๋ฉด "no login" ๊ฒฐ๊ณผ ๋ฐํ
- URL ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ idx๋ก ํด๋น ๋ฉ๋ชจ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฒ์ํ๋ค
- ์ฌ์ฉ์๊ฐ ์ ์ถํ ์ title๊ณผ contents๋ก ๋ฉ๋ชจ๋ฅผ ์ ๋ฐ์ดํธํ๊ณ , ์ฑ๊ณตํ๋ฉด "success" ๊ฒฐ๊ณผ๋ฅผ, ์คํจํ๋ฉด "error" ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
ret = query_db('SELECT * FROM memo where idx=?', [idx,])[0]
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if ret and title and contents:
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
(3) ํจ์น
1. secret key ํ๋ ์ฝ๋ฉ
flask ๋ ์ธ์ ๊ด๋ฆฌ ๋ฐ ์ํธํ๋ฅผ ์ํด app.secret_key๊ฐ ์ฌ์ฉ๋๋๋ฐ ์ด ๊ฐ์ด ํ๋์ฝ๋ฉ๋์ด์์ผ๋ฉด ์ธ์ ์ ์์กฐํ๋ ๋ฑ์ ๊ณต๊ฒฉ์ด ๊ฐ๋ฅํ๋ค. ๋ฐ๋ผ์ ๋๋ค๊ฐ์ ์์ฑํ์ฌ secert key๋ก ์ฌ์ฉํ๋๋ก ์์ ํด์ฃผ์๋ค.
import secrets
app.secret_key = secrets.token_hex(16)
2. login : fstring ์ฌ์ฉ -> prepared statement (SQL Injection)
ret = query_db(f"SELECT * FROM users where userid='{userid}' and password='{hashlib.sha256(password.encode()).hexdigest()}'" , one=True)
๋ก๊ทธ์ธํ ๋ ์ฌ์ฉํ๋ ์ฟผ๋ฆฌ๋ฌธ์์ Prepared Statements์ ์ฌ์ฉํ์ง ์๊ณ fstring์ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธ์ ํ๋ ๊ฒ์ ์ ์ ์๋ค. ์ด๋ฐ ๊ฒฝ์ฐ SQL Injection์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก, ๋ค๋ฅธ ์ฟผ๋ฆฌ๋ค๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์์ ํด์ฃผ์ด์ผ ํ๋ค.
sqlite๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก ํ๋ ์ด์คํ๋๋ ?๋ฅผ ์ฌ์ฉํด์ฃผ์ด์ผ ํ๋ค.
- SQLite, MySQL: ? ์ฌ์ฉ
- PostgreSQL: %s ๋๋ $1, $2 ๋ฑ ์ฌ์ฉ
- Oracle: :name ํ์ ์ฌ์ฉ
@app.route('/api/login', methods=['POST'])
def login():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
ret = query_db("SELECT * FROM users WHERE userid=? AND password=?",
(userid, hashlib.sha256(password.encode()).hexdigest()), one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
return jsonify(result="fail")
3. memoView : + ์ฐ์ฐ์ ์ฌ์ฉ -> prepared statement (SQL Injection)
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
memoViewํจ์์์ ๋ํ Prepared Statement๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ฌธ์์ด ์ฐ๊ฒฐ(+ ์ฐ์ฐ์)์ ํตํด ์ฟผ๋ฆฌ๋ฅผ ์์ฒญํ๋ค.
๋ฐ๋ผ์ SQL Injection์ ๋ฐฉ์ด๋ฅผ ์ํด ์์ ํด์ฃผ์ด์ผ ํ๋ค.
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo WHERE idx=?", (idx,), one=True)
4. memoView : render_template_string ํจ์ ์ฌ์ฉ (SSTI)
render_template_string ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด template๋ก ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ๊ฐ ์ฌ์ฉ์์ ์ ๋ ฅ ๋ฐ์ดํฐ์ ์ํด ๋ณ์กฐ๋ ์ ์์ผ๋ฉฐ, SSTI๊ฐ ๋ฐ์ํ ์ ์๋ค. ( ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ๋ ๋ฐ์ดํฐ(userid, title, contents)๊ฐ ํ ํ๋ฆฟ์ ์ง์ ์ฝ์ ๋ ํ์ ์ด ํ ํ๋ฆฟ์ด Jinja2 ์์ง์ผ๋ก ํด์๋๊ธฐ ๋๋ฌธ)
์ด๋ escape ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์์ ํด์ฃผ์๋ค.
from flask import escape
....
if mode == 'html':
html_content = f'''<p>Written by {escape(userid)}</p>
<h3>{escape(title)}</h3>
<pre>{escape(contents)}</pre>'''
return html_content
5. memoUpdate : ์ฌ์ฉ์ ๊ฒ์ฆ ๋ถ์กฑ (๊ถํ ์์น ์ทจ์ฝ์ )
memoUpdate ํจ์์์๋ ๋ฉ๋ชจ๋ฅผ ์ ๋ฐ์ดํธํ ๋ (์์ ํ ๋), ๋ก๊ทธ์ธ ์ฌ๋ถ๋ง ํ์ธํ๊ณ ๋ก๊ทธ์ธํ ์ฌ์ฉ์๊ฐ ๊ทธ ๋ฉ๋ชจ์ ์์ฑ์์ธ์ง ํ์ธํ์ง ์๋๋ค. ๋ฐ๋ผ์ ์ธ์ ์ uid๊ฐ์ ํ์ธํ๋ ๊ฒ์ฆ ๋ก์ง์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค.
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
# ๋ฉ๋ชจ ์ ๋ณด ์กฐํ
ret = query_db('SELECT * FROM memo WHERE idx=?', (idx,), one=True)
if not ret:
return jsonify(result="error", message="Memo not found")
# ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์
current_user = session.get('uid')
# ๋ฉ๋ชจ ์์ ์ ํ์ธ
if ret['userid'] != current_user:
return jsonify(result="error", message="Permission denied")
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",
(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
[์ต์ข ์ฝ๋]
#!/usr/bin/python3
from flask import Flask, request, render_template_string, g, session, jsonify, escape
import sqlite3
import os, hashlib, secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.environ['DATABASE'])
db.row_factory = sqlite3.Row
return db
def query_db(query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return "api-server"
@app.route('/api/me')
def me():
if session.get('uid'):
return jsonify(userid=session['uid'])
return jsonify(userid=None)
@app.route('/api/login', methods=['POST'])
def login():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
ret = query_db("SELECT * FROM users WHERE userid=? AND password=?",
(userid, hashlib.sha256(password.encode()).hexdigest()), one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
return jsonify(result="fail")
@app.route('/api/logout')
def logout():
session.pop('uid', None)
return jsonify(result="success")
@app.route('/api/join', methods=['POST'])
def join():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
conn = get_db()
cur = conn.cursor()
cur.execute("Insert into users values(?, ?);", (userid, hashlib.sha256(password.encode()).hexdigest()))
conn.commit()
return jsonify(result="success")
return jsonify(result="error")
@app.route('/api/memo/add', methods=['PUT'])
def memoAdd():
if not session.get('uid'):
return jsonify(result="no login")
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
ret = cur.execute("Insert into memo(userid, title, contents) values(?, ?, ?);", (userid, title, contents))
conn.commit()
return jsonify(result="success", memoidx=ret.lastrowid)
return jsonify(result="error")
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo WHERE idx=?", (idx,), one=True)
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
html_content = f'''<p>Written by {escape(userid)}</p>
<h3>{escape(title)}</h3>
<pre>{escape(contents)}</pre>'''
return html_content
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
# ๋ฉ๋ชจ ์ ๋ณด ์กฐํ
ret = query_db('SELECT * FROM memo WHERE idx=?', (idx,), one=True)
if not ret:
return jsonify(result="error", message="Memo not found")
# ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์
current_user = session.get('uid')
# ๋ฉ๋ชจ ์์ ์ ํ์ธ
if ret['userid'] != current_user:
return jsonify(result="error", message="Permission denied")
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",
(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
ํจ์น๊ฐ ์๋ฃ๋ ์ ์ฝ๋๋ฅผ ์ ์ถํ๋ฉด ํ๋๊ทธ๋ฅผ ์ป์ ์ ์๋ค.