index.js 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. const abort = (err) => {
  2. console.error(err)
  3. process.exit(1)
  4. }
  5. if (!process.env.PROXY_FORWARD_URL) abort('Please define forward URL in env PROXY_FORWARD_URL.')
  6. if (!process.env.PROXY_FORWARD_KEY) abort('Please define cipher key in env PROXY_FORWARD_KEY.')
  7. const key = Buffer.from(process.env.PROXY_FORWARD_KEY, 'base64')
  8. if (!key || key.length !== 32) abort('The cipher key shall be 32-byte base64 encoded string.')
  9. if (!process.env.PROXY_MOBILE_ID) abort('Please define mobile ID in env MOBILE_ID.')
  10. const _id = process.env.PROXY_MOBILE_ID
  11. const port = process.env.PROXY_PORT || '11451'
  12. const AnyProxy = require('anyproxy')
  13. const http = require('https')
  14. const crypto = require('crypto')
  15. const MP_HOST = 'mp.weixin.qq.com'
  16. const authTagLength = 16
  17. /**
  18. *
  19. * @param {string} url
  20. * @param {http.RequestOptions['headers']} headers
  21. */
  22. const postData = (url, headers) => {
  23. const nonce = crypto.randomBytes(12)
  24. const aad = crypto.randomBytes(16)
  25. const cipher = crypto.createCipheriv('chacha20-poly1305', key, nonce, { authTagLength })
  26. cipher.setAAD(aad)
  27. const encrypted = Buffer.concat([
  28. cipher.update(JSON.stringify({ _id, url, headers })),
  29. cipher.final()
  30. ]).toString('base64')
  31. const tag = cipher.getAuthTag().toString('base64')
  32. const body = { nonce: nonce.toString('base64'), encrypted, tag, aad: aad.toString('base64') }
  33. // console.log(body)
  34. const req = http.request(process.env.PROXY_FORWARD_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' } }, res => {
  35. let data = ''
  36. res.on('error', console.error)
  37. res.on('data', chunk => { data += chunk.toString() })
  38. res.on('end', () => console.log(res.statusCode, data))
  39. })
  40. req.on('error', console.error)
  41. req.write(JSON.stringify(body))
  42. req.end()
  43. }
  44. /**
  45. * @type {AnyProxy.RuleModule}
  46. */
  47. const rule = {
  48. summary: 'WeChat Credential',
  49. *beforeSendRequest({ url, requestOptions }) {
  50. if (requestOptions.hostname.toLowerCase() === MP_HOST) {
  51. if (url.includes('pass_ticket')) {
  52. // we like this, steal them
  53. postData(url, requestOptions.headers)
  54. if (requestOptions.path.includes('getverifyinfo')) {
  55. // just skip request
  56. return {
  57. response: {
  58. statusCode: 200,
  59. header: { 'Content-Type': 'text/html' },
  60. body: '<h1 style="color: green">NTR</h1>',
  61. }
  62. }
  63. }
  64. }
  65. }
  66. return null
  67. },
  68. *beforeDealHttpsRequest({ host }) {
  69. return host.includes(MP_HOST)
  70. }
  71. }
  72. const proxy = new AnyProxy.ProxyServer({
  73. port,
  74. rule
  75. })
  76. proxy.on('ready', () => {
  77. const fs = require('fs')
  78. const filename = 'proxy.lock'
  79. setInterval(() => {
  80. const time = new Date();
  81. try {
  82. fs.utimesSync(filename, time, time);
  83. } catch (e) {
  84. let fd = fs.openSync(filename, 'a');
  85. fs.closeSync(fd);
  86. }
  87. }, 1000)
  88. })
  89. proxy.on('error', console.error);
  90. proxy.start();