├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── v2 ├── 128.png ├── 16.png ├── 48.png ├── background.js ├── loader.js ├── manifest.json └── script.js └── v3 ├── 128.png ├── 16.png ├── 48.png ├── background.js ├── loader.js ├── manifest.json └── script.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Baw-Appie 2 | patreon: bawappie 3 | custom: ["https://paypal.me/pp121324", "https://l.bawappie.com/kakaopay"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Safe Transaction 2 | 3 | 여러분의 거래를 안전하게 지켜드리는 Safe Transaction 브라우저 확장 프로그램입니다. 4 | 5 | 표준 브라우저 확장을 따르도록 설계되어, 별도 프로그램 설치 없이 [@BawAppie](https://x.com/BawAppie)가 정한 한국 산업 표준 암호화를 이용하여 6 | 안전하게 아무 작업도 하지 않기 때문에 더욱 편리하게 사용할 수 있습니다. 물론 무슨 암호화인지는 모릅니다. 7 | 8 | 이 확장 프로그램을 설치하면 전자 금융 사기 예방 서비스가 가능하기 때문에 **무조건 설치**해야 합니다. 9 | 설치하고 싶지 않으세요? 그렇다면 대체 프로그램 설치 방법을 해당 사이트가 안내할 것입니다. 10 | 대체 프로그램과 행복한 시간이 되시길 바랍니다. 11 | 12 | > **경쟁 연구소로 부터 인정 받은 보안 기술** 13 | > 14 | > ![Approved by competitor](https://user-images.githubusercontent.com/27724108/221333588-dcbc68ab-b003-45e4-a049-d459187b8bc2.png) 15 | 16 | # 사용 전 확인하세요. 17 | 이 확장 프로그램이 사용하는 서버는 KT, SK, LG U+ 등 국내 ISP에서 제공하는 DNS 서버에서 차단되었습니다. 18 | [한국인터넷진흥원의 DNS 간섭에 대해 조사하고 있습니다. #8](https://github.com/Baw-Appie/SafeTransaction/issues/8) 19 | ![DNS 차단](https://github.com/Baw-Appie/SafeTransaction/assets/15670271/fce775d6-575d-4cb0-8df0-9f7fa1160f75) 20 | 개발자는 정부의 차단 시도에 대해 유감을 표합니다. 21 | 22 | ## 왜 차단되었을까요? 23 | KISA는 Safe Transaction 확장 프로그램이 보안 프로그램을 우회하기 때문에 공공의 위협을 초래할 수 있다고 합니다. 24 | 25 | ## 사실은 이렇습니다 26 | KISA의 주장과는 다르게 Safe Transaction은 보안 프로그램을 우회하지 않습니다. 27 | 아니 그렇다면 왜 이 확장 프로그램을 설치하면 보안 프로그램을 설치하라고 하지 않는 것일까요? 28 | Safe Transaction은 보안 프로그램보다 사용자 편의 중심의 보안 알고리즘을 사용한 29 | 더 강력한 보안 프로그램으로 대체하기 때문입니다. 30 | **여러분의 보안은 기존과 동일, 혹은 그 이상으로 안전하게 유지됩니다.** 31 | 32 | 내 컴퓨터에 프로그램을 설치하면 해당 프로그램은 컴퓨터의 권한을 모두 가져가게 됩니다. 33 | 하지만 Safe Transaction은 어떤가요? 브라우저 컨텍스트 안에서 격리되어 브라우저 밖을 빠져나올 수 없게 됩니다. 34 | 누군가 보안 프로그램의 취약점을 활용하여 내 컴퓨터를 해킹하는 이상한 사태가 발생할 수는 있어도, 35 | Safe Transaction의 취약점을 활용하여 해킹이 가능한 범위는 브라우저 탭, 그 이상은 불가합니다. 36 | 37 | 이러한 브라우저 격리 기술은 [WebKit](https://webkit.org), [Chromium](https://chromium.org), [Gecko](https://firefox-source-docs.mozilla.org/overview/gecko.html) 등을 통해 구현되어 [Apple](https://apple.com), [Google](https://google.com), [Microsoft](https://microsoft.com), [Mozilla](https://mozilla.org) 등 38 | 다양한 회사와 수 많은 자원 봉사자의 지원에 의하여 검증되었습니다. 39 | 그렇지만 보안 프로그램이라고 주장하는 것은 대체 누가 그 보안을 검증하였나요? 40 | 저는 모르겠습니다. 41 | 42 | ## 그래서 어떻게 할까요? 43 | 이 문제는 사실 개발자가 해결해야 할 문제입니다. 44 | 하지만 이러한 차단을 우회한다 하더라도 다시 차단된다면 불법 사이트가 도메인 바꾸는 것 마냥 저도 도메인 수백개를 사서 사용해야 하게 될 것입니다. 45 | 하지만 이 글을 보시고 계신 이용자님께서는 저에게 돈을 주지 않을 것을 알고 있습니다. 46 | 그렇기에 사용자 분들에게 근본적인 문제 해결이 가능한 [DNS 변경]을 권장드립니다. 47 | 48 | 위의 사유로 인하여 이 확장 프로그램을 사용하고자 한다면 네트워크의 DNS를 [Cloudflare DNS Resolver (1.1.1.1)](https://1.1.1.1) 또는 [Google Public DNS (8.8.8.8)](https://dns.google/) 등의 해외 서비스가 제공하는 DNS 서버로 변경하세요. 49 | 차단된 주소는 다음과 같습니다: 50 | - astx2-emulator.appie.dev 51 | - ipinside-emulator.appie.dev 52 | 53 | # 설치 54 | 55 | Chrome: https://chrome.google.com/webstore/detail/safe-transaction/hlknngdmefboagppfcddkbnnphbhdngk 56 | Firefox: https://addons.mozilla.org/addon/safe-transaction 57 | Whale: https://store.whale.naver.com/detail/fennejhonlpdndgjkhpgojjlnfbcjakk 58 | 59 | # 지원 현황 60 | 61 | | | 조회 | 거래(이체) | 비고 | 62 | | ------- | -- | ------ | ---------------------------------------------------- | 63 | | 국민은행 | ✔️ | ✔️ | KB국민인증서 로그인 | 64 | | 하나은행 | ✔️ | ✔️ | 하나인증서 로그인 | 65 | | 신한은행 | ✔️ | ✔️ | 신한인증서 로그인 | 66 | | 우리은행 | ❓ | ❓ | 확인 필요 | 67 | | 농협은행 | ✔️ | ✔️ | 금융인증서 로그인 | 68 | | 기업은행 | ✔️ | ✔️ | 금융인증서 로그인, 24.09.26 확인 | 69 | | SC제일은행 | ✔️ | ❌ | 금융인증서 로그인, INISAFE CrossWeb EX 설치시 이체 가능 | 70 | | 케이뱅크 기업 | ✔️ | ✔️ | 공동인증서 로그인, 25.05.22 케뱅비즈 전용 패치로 우회 | 71 | | OK저축은행 | ✔️ | ❌ | 금융인증서 로그인, 이체 시도시 NOS 통신 오류 (NOS E2E 필요) / 24.09.26 확인 | 72 | | 삼성증권[^삼성증권] | ✔️ | ❌ | 클라우드 인증서 로그인, SignKorea NA Certification Tool Kit 설치 필요 | 73 | [^삼성증권]: 클라우드 인증서 사용시 SignKorea NA Certification Tool Kit 설치가 되었음을 나타내는 Tampermonkey 스크립트로 로그인 가능함을 확인하였으나, 거래시 ASTx2의 E2E가 필요하여 거래 불가 74 | 75 | # 기능 및 특징 76 | 77 | - 안전한 거래를 강력하게 지원합니다. 78 | - IP 안에 무엇이 있을지 생각해보신 적 있으세요? 아무것도 없답니다. 79 | - 온라인 보안을 해드립니다 80 | 81 | # 만드는 것을 도와주신 감사한 분 82 | 83 | - [@BawAppie](https://twitter.com/BawAppie) 84 | 85 | ## Github Sponsor 86 | 이 프로젝트는 [한국모바일상품권](https://korgiftcard.io)으로부터 금전적인 지원을 많이 받았습니다. 87 | 88 | 89 | # 외부 서버 이용 안내 90 | 91 | 이 확장 프로그램은 외부 서버를 이용합니다. 그러므로 인터넷 없이는 작동하지 않습니다. 92 | 왜 개발자는 굳이 돈 주고 서버와 도메인을 구매한 뒤 외부 서버를 이용하도록 만들었을까요? 그 이유는 사용되는 암호화 알고리즘은 각 언어마다 사용 방법이 매우 다르며 기본 동작 마저 달라 구현을 옮기는 것이 쉽지 않기 때문입니다. 93 | Java 공화국, 대한민국인 만큼 구현에 사용한 암호화 PoC는 Kotlin JVM으로 작성되었으며 이러한 코드를 JavaScript로 다시 작성하는 것보다는, 암호화하는 서버를 만드는 것이 더욱 간단하고 쉬우며 개인 정보 유출에 더 큰 도움이 됩니다. 94 | 하지만 걱정할 필요가 없습니다. 이 확장 프로그램은 여러분의 개인정보를 수집하지 않습니다. 개발자는 여러분의 개인 정보를 존중하며 이 확장 프로그램이 사용하는 외부 서버는 여러분의 개인 정보를 저장하지 않습니다. 95 | 못 믿으시겠나요? 다시 생각해보세요. 이용자의 개인정보는 이미 공공재이므로 다른 곳에서 쉽게 구할 수 있는데 제가 뭐하러 수집을 하나요? 오히려 저장공간 낭비가 될 것입니다. 96 | 97 | 누군가는 저에게 그 정도 코드 옮기는 것은 그냥 AI로 '딸깍'하면 된다고 말하고는 합니다. 98 | 하지만 AI는 BouncyCastle이 얼마나 강력한 라이브러리인지 알고나 있을까요? 99 | 저는 BouncyCastle 없이 다시 구현하기가 두렵습니다. 100 | 101 | 102 | # 외부 서버의 소스 코드 103 | 104 | 이 개발자는 굳이 외부 서버를 사용하면서 외부 서버의 소스 코드를 공개하지 않았습니다. 105 | 왜일까요? 개발자의 입장에서 생각해봅시다. 이 확장 프로그램을 본 어떤 연구소는 이 확장 프로그램을 가만히 두지 않을 가능성이 높습니다. 그러므로 이 개발자는 나중에 이 확장 프로그램이 문제가 되었을 경우 도망가기 위하여 외부 서버를 공개하지 않았습니다. 개발자가 외부 서버를 닫아버리면 이 확장 프로그램은 고장나기 때문에 외부 서버는 킬 스위치의 역할을 할 수 있습니다. 106 | 하지만 개발자가 소스 코드를 공개했다면 어떨까요? 개발자가 서버를 닫더라도 누군가 서버를 다시 만들면 그만입니다. 저는 도망가야하기 때문에 공개하지 않겠습니다. 물론 도망가면 병역 면탈 또는 탈영으로 잡혀갑니다. 감사합니다. 107 | 108 | 109 | # 알고 있는 문제 110 | 111 | ## 1. 나는 MacOS 안쓰는데요? 112 | 보안 거래가 작동하면 그 사이트는 내가 macOS를 사용 중이라고 인식하여 이상한 파일을 던져주거나, 오작동하는 문제가 있을 수 있습니다. 113 | 이는 대부분의 사이트가 Windows에는 합법인척 하는 키로깅[1], 언제 터질지 모르는 커널 후킹 등 바이러스인지 뭔지 모를 괴랄한 짓을 하는 것에 비해 macOS는 간단한 짓만 하기 때문에 그런 것이니 참으시고 사용하세요. 114 | 115 | ## 2. 설치 후 유해 사이트라고 뜸 116 | 경쟁 제품에서 Safe Transaction이 사용하는 도메인을 피싱 사이트로 지정하여 발생하는 문제입니다. 117 | 왜 Safe Transaction과 경쟁 제품을 동시에 사용하시려고 하는지는 잘 모르겠지만, 해당 제품은 Safe Transaction를 보고 배가 아파서 정상 작동하지 못하도록 유해 사이트라는 거짓 작동을 하게 만든 것이 아닐까요? 118 | 위에서도 한번 언급했지만, Safe Transaction은 여러분의 민감한 데이터를 훔치치지 않습니다. 귀찮아서 끄지 않은 Nginx에 로깅되는 데이터에는 Cloudflare의 IP만 찍힐 뿐입니다. 119 | 저는 어떤 프로그램을 사용하는지는 사용하는 사람의 선택이니 어떤 것을 선택하도록 강요하지 않습니다. 그러므로 아래 몇 가지 해결 방법이 있습니다. 120 | 121 | ### 해결 방법 1: Safe Transaction 삭제 122 | 키로깅 기능부터, 사용중인 PC 정보 수집, 사용자 핑거프린팅, 접속 사이트 감청[1: 대한민국 해석 기준으로는 아님] 등 세계 최고 기능이 한 번에 포함된 프로그램을 Safe Transaction를 대신하여 사용하는 방법입니다. 123 | 수집을 동의하지 않으신거 같다고요? 걱정마세요. 경쟁사 개인정보 수집방침 깊숙한 곳에 숨겨져 있고, 따로 동의하라는 팝업을 띄우지도 않았거든요.[2] 124 | 125 | 이 글을 보는 사람이 이 방법을 사용하실 분은 없을 것이라고 생각합니다만, 아무튼 Safe Transaction을 지우면 문제가 해결됩니다. 126 | (Safe Transaction은 경쟁 제품과 달리 삭제시 클릭 한번으로 제거됩니다) 127 | 128 | ### 해결 방법 2: 경쟁 제품 삭제 129 | 누군지도 모르는 개발자가 적어둔 개인정보 수집하지 않는다는 글을 믿으시는 분에게 추천하는 방법입니다. 130 | 경쟁 제품을 삭제하면 해결됩니다. ([구라 제거기](https://teus.me/862?category=836336)를 활용하면 좋습니다) 131 | 132 | ### 해결 방법 3: ECH 사용 133 | 경쟁 제품은 http에서는 Host 헤더를, https에서는 [SNI 필드를 체크하여 유해 사이트 여부](https://brunch.co.kr/@searphiel9/46)를 확인합니다. 134 | https에서 이러한 감청을 막기 위한 여러가지의 시도들이 있었고 그 중 한 가지 방법으로 ECH(Encrypted Client Hello, [관련 링크](https://blog.cloudflare.com/encrypted-client-hello/))를 사용하는 방법입니다. 135 | Firefox에서 DoH를 활성화 한 뒤, about:config 로 이동하여 ```network.dns.echconfig.enabled```를 ```true```로 변경하면 됩니다. 136 | 대신 이 방법은 기본적으로 비활성화되어 있는 기능인 만큼 실험적이고 항상 잘 작동하진 않습니다. 137 | 138 | # 님만 모르는 문제 139 | 140 | 저는 착한 개발자이니 Safe Transaction을 사용함으로써 발생하는 모든 문제는 여러분에게 무상으로 제공하겠습니다. 사양하지 않으셔도 됩니다. 141 | 142 | [1] 대한민국 (유사) 유권해석 기준으로 처음부터 단대단 암호화가 아닌 제3자가 디코딩 후 열람 할 수 있는 사항을 보는 것은 "감청"에 해당되지 않으므로, 키로거는 "감청" 이 아닙니다.
143 | [2] 경쟁사 개발사인 모 연구소에서는 이런 프로그램을 PUP (Potentially Unwanted Program) 이라고 부르는 것 같아요. 144 | -------------------------------------------------------------------------------- /v2/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baw-Appie/SafeTransaction/da3c350f184a2cbcfa14cf48d36e4c889843a573/v2/128.png -------------------------------------------------------------------------------- /v2/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baw-Appie/SafeTransaction/da3c350f184a2cbcfa14cf48d36e4c889843a573/v2/16.png -------------------------------------------------------------------------------- /v2/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baw-Appie/SafeTransaction/da3c350f184a2cbcfa14cf48d36e4c889843a573/v2/48.png -------------------------------------------------------------------------------- /v2/background.js: -------------------------------------------------------------------------------- 1 | browser.webRequest.onBeforeRequest.addListener( 2 | details => { 3 | const url = new URL(details.url); 4 | if(url.host === "127.0.0.1:55920" || url.host === "lx.astxsvc.com:55920") return { redirectUrl: "https://astx2-emulator.appie.dev" + url.pathname + url.search }; 5 | if(url.host === "127.0.0.1:21300") return { redirectUrl: "https://ipinside-emulator.appie.dev" + url.pathname + url.search }; 6 | if(url.host === "127.0.0.1:14440") return { redirectUrl: "https://nos-emulator.appie.dev" + url.pathname + url.search }; 7 | }, 8 | { 9 | urls: [""] 10 | }, 11 | ["blocking"] 12 | ) -------------------------------------------------------------------------------- /v2/loader.js: -------------------------------------------------------------------------------- 1 | var s = document.createElement('script'); 2 | s.src = chrome.runtime.getURL('script.js'); 3 | s.onload = function() { 4 | this.remove(); 5 | }; 6 | (document.head || document.documentElement).appendChild(s); -------------------------------------------------------------------------------- /v2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Safe Transaction", 3 | "description" : "Secure your transaction", 4 | "version": "1.1.1", 5 | "manifest_version": 2, 6 | "icons": { 7 | "16": "16.png", 8 | "48": "48.png", 9 | "128": "128.png" 10 | }, 11 | "permissions": [ 12 | "webRequest", 13 | "", 14 | "https://lx.astxsvc.com:55920/*", 15 | "https://127.0.0.1:55920/*", 16 | "https://127.0.0.1:21300/*", 17 | "https://127.0.0.1:14440/*", 18 | "webRequestBlocking" 19 | ], 20 | "web_accessible_resources": ["script.js"], 21 | "content_scripts": [ 22 | { 23 | "run_at": "document_start", 24 | "matches": [""], 25 | "js": ["loader.js"], 26 | "all_frames": true 27 | } 28 | ], 29 | "background": { 30 | "scripts": ["background.js"] 31 | } 32 | } -------------------------------------------------------------------------------- /v2/script.js: -------------------------------------------------------------------------------- 1 | const _SafeTransactionNOSVKeypadPatchInitializer = () => { 2 | const callback = async (mutationList, observer) => { 3 | for (const mutation of mutationList) { 4 | if (mutation.type === "attributes" && mutation.attributeName == "class" && mutation.target.classList.contains("nppfs-npv")) { 5 | const { target } = mutation 6 | window.npVCtrl.hideAll() 7 | target.classList.remove("nppfs-npv") 8 | const newTarget = target.cloneNode(true) 9 | target.parentNode.replaceChild(newTarget, target); 10 | 11 | const encryptedInput = document.getElementsByName(`__KI_${target.name}`)[0] 12 | const makeKI = await fetch("https://127.0.0.1:14440/_/makeKI", { method: "POST", body: encryptedInput.value }) 13 | const response = await makeKI.text() 14 | const splitResponse = response.split("|") 15 | encryptedInput.value = splitResponse[0] 16 | newTarget.removeAttribute("readonly") 17 | newTarget.setAttribute("x-safetransaction-kh", splitResponse[1]) 18 | newTarget.addEventListener("blur", onblurCallback) 19 | console.log("[SafeTransaction] NOS Virtual Keypad Replaced", newTarget) 20 | } 21 | } 22 | } 23 | const observer = new MutationObserver(callback); 24 | observer.observe(document, { attributes: true, childList: true, subtree: true }); 25 | 26 | const onblurCallback = async e => { 27 | const { target } = e 28 | const kh = target.getAttribute("x-safetransaction-kh") 29 | const khInput = document.getElementsByName(`__KH_${kh}`)[0] 30 | khInput.value = target.value 31 | } 32 | 33 | // const npkencryptInputs = document.querySelectorAll('input[npkencrypt]'); 34 | // if(npkencryptInputs.length == 0) { 35 | // const fakeNuaGetter = () => "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36" 36 | // Object.defineProperty(window, "nua", { get: fakeNuaGetter }); 37 | // } 38 | } 39 | 40 | const _SafeTransactionUserAgentPatcher = () => { 41 | let needToPatch = false 42 | if (window.$ASTX2) needToPatch = true 43 | if (needToPatch) { 44 | const fakePlatformGetter = () => "MacIntel" 45 | if (Object.defineProperty) { 46 | Object.defineProperty(navigator, "platform", { get: fakePlatformGetter }); 47 | Object.defineProperty(Navigator.prototype, "platform", { get: fakePlatformGetter }); 48 | } else if (Object.prototype.__defineGetter__) { 49 | navigator.__defineGetter__("platform", fakePlatformGetter); 50 | Navigator.prototype.__defineGetter__("platform", fakePlatformGetter); 51 | } 52 | const fakeUserAgentGetter = () => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" 53 | if (Object.defineProperty) { 54 | Object.defineProperty(navigator, "userAgent", { get: fakeUserAgentGetter }); 55 | Object.defineProperty(Navigator.prototype, "userAgent", { get: fakeUserAgentGetter }); 56 | } else if (Object.prototype.__defineGetter__) { 57 | navigator.__defineGetter__("userAgent", fakeUserAgentGetter); 58 | Navigator.prototype.__defineGetter__("userAgent", fakeUserAgentGetter); 59 | } 60 | const fakeAppVersionGetter = () => "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" 61 | if (Object.defineProperty) { 62 | Object.defineProperty(navigator, "appVersion", { get: fakeAppVersionGetter }); 63 | Object.defineProperty(Navigator.prototype, "appVersion", { get: fakeAppVersionGetter }); 64 | } else if (Object.prototype.__defineGetter__) { 65 | navigator.__defineGetter__("appVersion", fakeAppVersionGetter); 66 | Navigator.prototype.__defineGetter__("appVersion", fakeAppVersionGetter); 67 | } 68 | } 69 | } 70 | 71 | const _SafeTransactionKBankBizPatcher = () => { 72 | if (location.host != "biz.kbanknow.com") return 73 | Object.defineProperty(window, "SECURE_TYPE", { value: "", writable: false }); 74 | 75 | const originalOpen = XMLHttpRequest.prototype.open; 76 | const originalSend = XMLHttpRequest.prototype.send; 77 | 78 | XMLHttpRequest.prototype.open = function (method, url) { 79 | this._url = url; 80 | return originalOpen.apply(this, arguments); 81 | }; 82 | 83 | XMLHttpRequest.prototype.send = function () { 84 | if (typeof this._url === 'string' && this._url.startsWith("/product/initech/crossweb/webui/conf/customerConf.json")) { 85 | this.addEventListener('readystatechange', e => { 86 | if (this.readyState != 4) return 87 | const newText = this.responseText.replaceAll(`"USE":"Y"`, `"USE":"N"`) 88 | Object.defineProperty(this, 'responseText', { get: () => { return newText; } }); 89 | Object.defineProperty(this, 'response', { get: () => { return newText; } }); 90 | }); 91 | } 92 | return originalSend.apply(this, arguments); 93 | }; 94 | } 95 | 96 | const _SafeTransactionHomeTaxPatcher = () => { 97 | if (location.host != "hometax.go.kr") return 98 | const originalOpen = XMLHttpRequest.prototype.open; 99 | const originalSend = XMLHttpRequest.prototype.send; 100 | 101 | XMLHttpRequest.prototype.open = function (method, url) { 102 | this._url = url; 103 | return originalOpen.apply(this, arguments); 104 | }; 105 | 106 | XMLHttpRequest.prototype.send = function () { 107 | if (typeof this._url === 'string' && this._url.includes("js/comm/raonsecure/transkey/rsa_oaep_files/rsa_oaep-min.js")) { 108 | console.log("[SafeTransaction] Patching XMLHttpRequest for HomeTax RSA OAEP"); 109 | this.addEventListener('readystatechange', e => { 110 | if (this.readyState != 4) return 111 | Object.defineProperty(this, 'statusText', { get: () => { return "Internal Server Error"; } }); 112 | Object.defineProperty(this, 'status', { get: () => { return 500; } }); 113 | }); 114 | } 115 | return originalSend.apply(this, arguments); 116 | }; 117 | } 118 | 119 | // _SafeTransactionNOSVKeypadPatchInitializer() 120 | _SafeTransactionKBankBizPatcher() 121 | _SafeTransactionHomeTaxPatcher() 122 | const _SafeTransactionOnWindowLoaded = () => { 123 | _SafeTransactionUserAgentPatcher() 124 | } 125 | 126 | 127 | if (document.readyState === "complete" || document.readyState === "interactive") { 128 | setTimeout(_SafeTransactionOnWindowLoaded, 1); 129 | } else { 130 | document.addEventListener("DOMContentLoaded", _SafeTransactionOnWindowLoaded); 131 | } -------------------------------------------------------------------------------- /v3/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baw-Appie/SafeTransaction/da3c350f184a2cbcfa14cf48d36e4c889843a573/v3/128.png -------------------------------------------------------------------------------- /v3/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baw-Appie/SafeTransaction/da3c350f184a2cbcfa14cf48d36e4c889843a573/v3/16.png -------------------------------------------------------------------------------- /v3/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Baw-Appie/SafeTransaction/da3c350f184a2cbcfa14cf48d36e4c889843a573/v3/48.png -------------------------------------------------------------------------------- /v3/background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onInstalled.addListener(() => { 2 | chrome.declarativeNetRequest.updateDynamicRules({ 3 | addRules: [ 4 | { 5 | id: 1, 6 | priority: 1, 7 | action: { 8 | type: 'redirect', 9 | redirect: { regexSubstitution: 'https://astx2-emulator.appie.dev/\\1' } 10 | }, 11 | condition: { 12 | regexFilter: 'https://127.0.0.1:55920/(.*)', 13 | resourceTypes: ["script", "xmlhttprequest"] 14 | } 15 | }, 16 | { 17 | id: 2, 18 | priority: 2, 19 | action: { 20 | type: 'redirect', 21 | redirect: { regexSubstitution: 'https://astx2-emulator.appie.dev/\\1' } 22 | }, 23 | condition: { 24 | regexFilter: 'https://lx.astxsvc.com:55920/(.*)', 25 | resourceTypes: ["script", "xmlhttprequest"] 26 | } 27 | }, 28 | { 29 | id: 3, 30 | priority: 3, 31 | action: { 32 | type: 'redirect', 33 | redirect: { regexSubstitution: 'https://ipinside-emulator.appie.dev/\\1' } 34 | }, 35 | condition: { 36 | regexFilter: 'https://127.0.0.1:21300/(.*)', 37 | resourceTypes: ["script", "xmlhttprequest"] 38 | }, 39 | }, 40 | { 41 | id: 4, 42 | priority: 4, 43 | action: { 44 | type: 'redirect', 45 | redirect: { regexSubstitution: 'https://nos-emulator.appie.dev/\\1' } 46 | }, 47 | condition: { 48 | regexFilter: 'https://127.0.0.1:14440/(.*)', 49 | resourceTypes: ["script", "xmlhttprequest", "image"] 50 | } 51 | }, 52 | ], 53 | removeRuleIds: [1,2,3,4] 54 | }) 55 | }) 56 | 57 | -------------------------------------------------------------------------------- /v3/loader.js: -------------------------------------------------------------------------------- 1 | var s = document.createElement('script'); 2 | s.src = chrome.runtime.getURL('script.js'); 3 | s.onload = function() { 4 | this.remove(); 5 | }; 6 | (document.head || document.documentElement).appendChild(s); -------------------------------------------------------------------------------- /v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Safe Transaction", 3 | "description" : "Secure your transaction", 4 | "version": "1.1.1", 5 | "manifest_version": 3, 6 | "icons": { 7 | "16": "16.png", 8 | "48": "48.png", 9 | "128": "128.png" 10 | }, 11 | "permissions": [ 12 | "declarativeNetRequest" 13 | ], 14 | "host_permissions": [ 15 | "" 16 | ], 17 | "web_accessible_resources": [{ 18 | "resources": ["script.js"], 19 | "matches": [""] 20 | }], 21 | "content_scripts": [ 22 | { 23 | "run_at": "document_start", 24 | "matches": [""], 25 | "js": ["loader.js"], 26 | "all_frames": true 27 | } 28 | ], 29 | "background": { 30 | "service_worker": "background.js" 31 | } 32 | } -------------------------------------------------------------------------------- /v3/script.js: -------------------------------------------------------------------------------- 1 | const _SafeTransactionNOSVKeypadPatchInitializer = () => { 2 | const callback = async (mutationList, observer) => { 3 | for (const mutation of mutationList) { 4 | if (mutation.type === "attributes" && mutation.attributeName == "class" && mutation.target.classList.contains("nppfs-npv")) { 5 | const { target } = mutation 6 | window.npVCtrl.hideAll() 7 | target.classList.remove("nppfs-npv") 8 | const newTarget = target.cloneNode(true) 9 | target.parentNode.replaceChild(newTarget, target); 10 | 11 | const encryptedInput = document.getElementsByName(`__KI_${target.name}`)[0] 12 | const makeKI = await fetch("https://127.0.0.1:14440/_/makeKI", { method: "POST", body: encryptedInput.value }) 13 | const response = await makeKI.text() 14 | const splitResponse = response.split("|") 15 | encryptedInput.value = splitResponse[0] 16 | newTarget.removeAttribute("readonly") 17 | newTarget.setAttribute("x-safetransaction-kh", splitResponse[1]) 18 | newTarget.addEventListener("blur", onblurCallback) 19 | console.log("[SafeTransaction] NOS Virtual Keypad Replaced", newTarget) 20 | } 21 | } 22 | } 23 | const observer = new MutationObserver(callback); 24 | observer.observe(document, { attributes: true, childList: true, subtree: true }); 25 | 26 | const onblurCallback = async e => { 27 | const { target } = e 28 | const kh = target.getAttribute("x-safetransaction-kh") 29 | const khInput = document.getElementsByName(`__KH_${kh}`)[0] 30 | khInput.value = target.value 31 | } 32 | 33 | // const npkencryptInputs = document.querySelectorAll('input[npkencrypt]'); 34 | // if(npkencryptInputs.length == 0) { 35 | // const fakeNuaGetter = () => "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36" 36 | // Object.defineProperty(window, "nua", { get: fakeNuaGetter }); 37 | // } 38 | } 39 | 40 | const _SafeTransactionUserAgentPatcher = () => { 41 | let needToPatch = false 42 | if (window.$ASTX2) needToPatch = true 43 | if (needToPatch) { 44 | const fakePlatformGetter = () => "MacIntel" 45 | if (Object.defineProperty) { 46 | Object.defineProperty(navigator, "platform", { get: fakePlatformGetter }); 47 | Object.defineProperty(Navigator.prototype, "platform", { get: fakePlatformGetter }); 48 | } else if (Object.prototype.__defineGetter__) { 49 | navigator.__defineGetter__("platform", fakePlatformGetter); 50 | Navigator.prototype.__defineGetter__("platform", fakePlatformGetter); 51 | } 52 | const fakeUserAgentGetter = () => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" 53 | if (Object.defineProperty) { 54 | Object.defineProperty(navigator, "userAgent", { get: fakeUserAgentGetter }); 55 | Object.defineProperty(Navigator.prototype, "userAgent", { get: fakeUserAgentGetter }); 56 | } else if (Object.prototype.__defineGetter__) { 57 | navigator.__defineGetter__("userAgent", fakeUserAgentGetter); 58 | Navigator.prototype.__defineGetter__("userAgent", fakeUserAgentGetter); 59 | } 60 | const fakeAppVersionGetter = () => "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" 61 | if (Object.defineProperty) { 62 | Object.defineProperty(navigator, "appVersion", { get: fakeAppVersionGetter }); 63 | Object.defineProperty(Navigator.prototype, "appVersion", { get: fakeAppVersionGetter }); 64 | } else if (Object.prototype.__defineGetter__) { 65 | navigator.__defineGetter__("appVersion", fakeAppVersionGetter); 66 | Navigator.prototype.__defineGetter__("appVersion", fakeAppVersionGetter); 67 | } 68 | } 69 | } 70 | 71 | const _SafeTransactionKBankBizPatcher = () => { 72 | if (location.host != "biz.kbanknow.com") return 73 | Object.defineProperty(window, "SECURE_TYPE", { value: "", writable: false }); 74 | 75 | const originalOpen = XMLHttpRequest.prototype.open; 76 | const originalSend = XMLHttpRequest.prototype.send; 77 | 78 | XMLHttpRequest.prototype.open = function (method, url) { 79 | this._url = url; 80 | return originalOpen.apply(this, arguments); 81 | }; 82 | 83 | XMLHttpRequest.prototype.send = function () { 84 | if (typeof this._url === 'string' && this._url.startsWith("/product/initech/crossweb/webui/conf/customerConf.json")) { 85 | this.addEventListener('readystatechange', e => { 86 | if (this.readyState != 4) return 87 | const newText = this.responseText.replaceAll(`"USE":"Y"`, `"USE":"N"`) 88 | Object.defineProperty(this, 'responseText', { get: () => { return newText; } }); 89 | Object.defineProperty(this, 'response', { get: () => { return newText; } }); 90 | }); 91 | } 92 | return originalSend.apply(this, arguments); 93 | }; 94 | } 95 | 96 | const _SafeTransactionHomeTaxPatcher = () => { 97 | if (location.host != "hometax.go.kr") return 98 | const originalOpen = XMLHttpRequest.prototype.open; 99 | const originalSend = XMLHttpRequest.prototype.send; 100 | 101 | XMLHttpRequest.prototype.open = function (method, url) { 102 | this._url = url; 103 | return originalOpen.apply(this, arguments); 104 | }; 105 | 106 | XMLHttpRequest.prototype.send = function () { 107 | if (typeof this._url === 'string' && this._url.includes("js/comm/raonsecure/transkey/rsa_oaep_files/rsa_oaep-min.js")) { 108 | console.log("[SafeTransaction] Patching XMLHttpRequest for HomeTax RSA OAEP"); 109 | this.addEventListener('readystatechange', e => { 110 | if (this.readyState != 4) return 111 | Object.defineProperty(this, 'statusText', { get: () => { return "Internal Server Error"; } }); 112 | Object.defineProperty(this, 'status', { get: () => { return 500; } }); 113 | }); 114 | } 115 | return originalSend.apply(this, arguments); 116 | }; 117 | } 118 | 119 | // _SafeTransactionNOSVKeypadPatchInitializer() 120 | _SafeTransactionKBankBizPatcher() 121 | _SafeTransactionHomeTaxPatcher() 122 | const _SafeTransactionOnWindowLoaded = () => { 123 | _SafeTransactionUserAgentPatcher() 124 | } 125 | 126 | 127 | if (document.readyState === "complete" || document.readyState === "interactive") { 128 | setTimeout(_SafeTransactionOnWindowLoaded, 1); 129 | } else { 130 | document.addEventListener("DOMContentLoaded", _SafeTransactionOnWindowLoaded); 131 | } --------------------------------------------------------------------------------