AbstractInjectionBit.java

1
package com.jsql.model.injection.strategy.blind;
2
3
import com.jsql.model.InjectionModel;
4
import com.jsql.model.accessible.DataAccess;
5
import com.jsql.model.bean.util.Interaction;
6
import com.jsql.model.bean.util.Request;
7
import com.jsql.model.exception.InjectionFailureException;
8
import com.jsql.model.exception.StoppedByUserSlidingException;
9
import com.jsql.model.injection.strategy.blind.callable.AbstractCallableBit;
10
import com.jsql.model.suspendable.AbstractSuspendable;
11
import com.jsql.util.LogLevelUtil;
12
import org.apache.logging.log4j.LogManager;
13
import org.apache.logging.log4j.Logger;
14
15
import java.util.ArrayList;
16
import java.util.List;
17
import java.util.concurrent.CompletionService;
18
import java.util.concurrent.ExecutionException;
19
import java.util.concurrent.ExecutorCompletionService;
20
import java.util.concurrent.ExecutorService;
21
import java.util.concurrent.atomic.AtomicInteger;
22
23
public abstract class AbstractInjectionBit<T extends AbstractCallableBit<T>> {
24
    
25
    /**
26
     * Log4j logger sent to view.
27
     */
28
    private static final Logger LOGGER = LogManager.getRootLogger();
29
30
    // Every FALSE SQL statements will be checked,
31
    // more statements means a more robust application
32
    protected List<String> falsyBit;
33
    protected List<String> falsyBin;
34
35
    // Every TRUE SQL statements will be checked,
36
    // more statements means a more robust application
37
    protected List<String> truthyBit;
38
    protected List<String> truthyBin;
39
40
    public enum BlindOperator {
41
        AND, OR, STACK, NO_MODE
42
    }
43
44
    protected final InjectionModel injectionModel;
45
    protected final BlindOperator blindOperator;
46
    
47
    protected AbstractInjectionBit(InjectionModel injectionModel, BlindOperator blindOperator) {
48
        this.injectionModel = injectionModel;
49
        this.blindOperator = blindOperator;
50
        this.falsyBit = this.injectionModel.getMediatorVendor().getVendor().instance().getFalsyBit();
51
        this.truthyBit = this.injectionModel.getMediatorVendor().getVendor().instance().getTruthyBit();
52
        this.falsyBin = this.injectionModel.getMediatorVendor().getVendor().instance().getFalsyBin();
53
        this.truthyBin = this.injectionModel.getMediatorVendor().getVendor().instance().getTruthyBin();
54
    }
55
56
    /**
57
     * Start one test to verify if boolean works.
58
     * @return true if boolean method is confirmed
59
     */
60
    public abstract boolean isInjectable() throws StoppedByUserSlidingException;
61
62
    public abstract void initNextChar(
63
        String sqlQuery,
64
        List<char[]> bytes,
65
        AtomicInteger indexChar,
66
        CompletionService<T> taskCompletionService,
67
        AtomicInteger countTasksSubmitted,
68
        T currentCallable  // required by sequential calls like binary search
69
    );
70
71
    public abstract char[] initMaskAsciiChar(List<char[]> bytes, T currentCallable);
72
73
    /**
74
     * Display a message to explain how is blind/time working.
75
     */
76
    public abstract String getInfoMessage();
77
78
    /**
79
     * Process the whole boolean injection, character by character, bit by bit.
80
     * @param sqlQuery SQL query
81
     * @param suspendable Action a user can stop
82
     * @return Final string: SQLiABCDEF...
83
     */
84
    public String inject(String sqlQuery, AbstractSuspendable suspendable) throws StoppedByUserSlidingException {
85
        // List of the characters, each one represented by an array of 8 bits
86
        // e.g. SQLi: bytes[0] => 01010011:S, bytes[1] => 01010001:Q ...
87
        List<char[]> bytes = new ArrayList<>();
88
        var indexChar = new AtomicInteger(0);  // current char position
89
90
        // Concurrent URL requests
91
        ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableAbstractBoolean");
92
        CompletionService<T> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
93
94
        var countTasksSubmitted = new AtomicInteger(0);
95
        var countBadAsciiCode = new AtomicInteger(0);
96
97 1 1. inject : removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBit::initNextChar → NO_COVERAGE
        this.initNextChar(sqlQuery, bytes, indexChar, taskCompletionService, countTasksSubmitted, null);
98
99
        // Process the job until there is no more active task,
100
        // in other word until all HTTP requests are done
101 2 1. inject : negated conditional → NO_COVERAGE
2. inject : changed conditional boundary → NO_COVERAGE
        while (countTasksSubmitted.get() > 0) {
102 1 1. inject : negated conditional → NO_COVERAGE
            if (suspendable.isSuspended()) {
103
                String result = this.stop(bytes, taskExecutor);
104
                throw new StoppedByUserSlidingException(result);
105
            }
106
            
107
            try {
108
                var currentCallable = taskCompletionService.take().get();  // URL call done
109
                countTasksSubmitted.decrementAndGet();  // one task just ended
110
                
111
                // If SQL result is not empty, then add a new unknown character and define a new array of 7 undefined bit.
112
                // Then add 7 bits requests for that new character.
113
                var isComplete = this.injectCharacter(bytes, countTasksSubmitted, countBadAsciiCode, currentCallable);
114 2 1. inject : negated conditional → NO_COVERAGE
2. inject : negated conditional → NO_COVERAGE
                if (isComplete || currentCallable.isBinary()) {  // prevents bitwise overload new char init on each bit
115 1 1. inject : removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBit::initNextChar → NO_COVERAGE
                    this.initNextChar(sqlQuery, bytes, indexChar, taskCompletionService, countTasksSubmitted, currentCallable);
116
                }
117
118
                String result = AbstractInjectionBit.convert(bytes);
119 1 1. inject : negated conditional → NO_COVERAGE
                if (result.matches("(?s).*"+ DataAccess.TRAIL_RGX +".*")) {
120 1 1. inject : removed call to java/util/concurrent/atomic/AtomicInteger::set → NO_COVERAGE
                    countTasksSubmitted.set(0);
121
                    break;
122
                }
123
            } catch (InterruptedException e) {
124
                LOGGER.log(LogLevelUtil.IGNORE, e, e);
125 1 1. inject : removed call to java/lang/Thread::interrupt → NO_COVERAGE
                Thread.currentThread().interrupt();
126
            } catch (ExecutionException e) {
127
                LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
128
            } catch (InjectionFailureException e) {
129
                LOGGER.log(LogLevelUtil.CONSOLE_ERROR, e.getMessage());
130
                break;
131
            }
132
        }
133 1 1. inject : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::inject → NO_COVERAGE
        return this.stop(bytes, taskExecutor);
134
    }
135
136
    private static String convert(List<char[]> bytes) {
137
        var result = new StringBuilder();
138
        for (char[] c: bytes) {
139
            try {
140
                var charCode = Integer.parseInt(new String(c), 2);
141
                var str = Character.toString((char) charCode);
142
                result.append(str);
143
            } catch (NumberFormatException err) {
144
                // Ignore, byte string not fully constructed (0x1x010x)
145
            }
146
        }
147 1 1. convert : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::convert → NO_COVERAGE
        return result.toString();
148
    }
149
150
    protected boolean injectCharacter(List<char[]> bytes, AtomicInteger countTasksSubmitted, AtomicInteger countBadAsciiCode, T currentCallable) throws InjectionFailureException {
151
        // Process url that has just checked one bit, convert bits to chars,
152
        // and change current bit from undefined to 0 or 1
153
        char[] maskAsciiChar = this.initMaskAsciiChar(bytes, currentCallable);
154
        var asciiCodeBit = new String(maskAsciiChar);
155
        var isComplete = false;
156
157
        // Inform the View if bits array is complete, else nothing #Need fix
158 1 1. injectCharacter : negated conditional → NO_COVERAGE
        if (asciiCodeBit.matches("^[01]{8}$")) {
159
            var asciiCode = Integer.parseInt(asciiCodeBit, 2);
160 2 1. injectCharacter : negated conditional → NO_COVERAGE
2. injectCharacter : negated conditional → NO_COVERAGE
            if (asciiCode == 127 || asciiCode == 0) {  // Stop if many 11111111, 01111111 or 00000000
161 3 1. injectCharacter : changed conditional boundary → NO_COVERAGE
2. injectCharacter : negated conditional → NO_COVERAGE
3. injectCharacter : negated conditional → NO_COVERAGE
                if (countTasksSubmitted.get() != 0 && countBadAsciiCode.get() > 15) {
162
                    throw new InjectionFailureException("Boolean false positive, stopping...");
163
                }
164
                countBadAsciiCode.incrementAndGet();
165
            }
166
167 1 1. injectCharacter : removed call to com/jsql/model/injection/strategy/blind/callable/AbstractCallableBit::setCharText → NO_COVERAGE
            currentCallable.setCharText(Character.toString((char) asciiCode));
168
            
169
            var interaction = new Request();
170 1 1. injectCharacter : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
            interaction.setMessage(Interaction.MESSAGE_BINARY);
171 1 1. injectCharacter : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
            interaction.setParameters(
172
                asciiCodeBit
173
                + "="
174
                + currentCallable.getCharText()
175
                .replace("\\n", "\\\\\\n")
176
                .replace("\\r", "\\\\\\r")
177
                .replace("\\t", "\\\\\\t")
178
            );
179 1 1. injectCharacter : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
            this.injectionModel.sendToViews(interaction);
180
            isComplete = true;
181
        }
182 2 1. injectCharacter : replaced boolean return with true for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::injectCharacter → NO_COVERAGE
2. injectCharacter : replaced boolean return with false for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::injectCharacter → NO_COVERAGE
        return isComplete;
183
    }
184
185
    private String stop(List<char[]> bytes, ExecutorService taskExecutor) {
186 1 1. stop : removed call to com/jsql/util/ThreadUtil::shutdown → NO_COVERAGE
        this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
187
188
        // Get current progress and display
189
        var result = new StringBuilder();
190
        
191
        for (char[] c: bytes) {
192
            try {
193
                var charCode = Integer.parseInt(new String(c), 2);
194
                var str = Character.toString((char) charCode);
195
                result.append(str);
196
            } catch (NumberFormatException err) {
197
                // Byte string not fully constructed : 0x1x010x
198
                // Ignore
199
            }
200
        }
201 1 1. stop : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::stop → NO_COVERAGE
        return result.toString();
202
    }
203
204
    /**
205
     * Run a HTTP call via the model.
206
     * @param urlString URL to inject
207
     * @return Source code
208
     */
209
    public String callUrl(String urlString, String metadataInjectionProcess) {
210 1 1. callUrl : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::callUrl → NO_COVERAGE
        return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
211
    }
212
213
    public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBit<?> callableBoolean) {
214 1 1. callUrl : replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::callUrl → NO_COVERAGE
        return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
215
    }
216
217
    public BlindOperator getBooleanMode() {
218 1 1. getBooleanMode : replaced return value with null for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::getBooleanMode → NO_COVERAGE
        return this.blindOperator;
219
    }
220
221
    protected static char[] getBitsUnset() {
222 1 1. getBitsUnset : replaced return value with null for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::getBitsUnset → NO_COVERAGE
        return new char[]{ '0', 'x', 'x', 'x', 'x', 'x', 'x', 'x' };
223
    }
224
}

Mutations

97

1.1
Location : inject
Killed by : none
removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBit::initNextChar → NO_COVERAGE

101

1.1
Location : inject
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : inject
Killed by : none
changed conditional boundary → NO_COVERAGE

102

1.1
Location : inject
Killed by : none
negated conditional → NO_COVERAGE

114

1.1
Location : inject
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : inject
Killed by : none
negated conditional → NO_COVERAGE

115

1.1
Location : inject
Killed by : none
removed call to com/jsql/model/injection/strategy/blind/AbstractInjectionBit::initNextChar → NO_COVERAGE

119

1.1
Location : inject
Killed by : none
negated conditional → NO_COVERAGE

120

1.1
Location : inject
Killed by : none
removed call to java/util/concurrent/atomic/AtomicInteger::set → NO_COVERAGE

125

1.1
Location : inject
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

133

1.1
Location : inject
Killed by : none
replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::inject → NO_COVERAGE

147

1.1
Location : convert
Killed by : none
replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::convert → NO_COVERAGE

158

1.1
Location : injectCharacter
Killed by : none
negated conditional → NO_COVERAGE

160

1.1
Location : injectCharacter
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : injectCharacter
Killed by : none
negated conditional → NO_COVERAGE

161

1.1
Location : injectCharacter
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : injectCharacter
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : injectCharacter
Killed by : none
negated conditional → NO_COVERAGE

167

1.1
Location : injectCharacter
Killed by : none
removed call to com/jsql/model/injection/strategy/blind/callable/AbstractCallableBit::setCharText → NO_COVERAGE

170

1.1
Location : injectCharacter
Killed by : none
removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE

171

1.1
Location : injectCharacter
Killed by : none
removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE

179

1.1
Location : injectCharacter
Killed by : none
removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE

182

1.1
Location : injectCharacter
Killed by : none
replaced boolean return with true for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::injectCharacter → NO_COVERAGE

2.2
Location : injectCharacter
Killed by : none
replaced boolean return with false for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::injectCharacter → NO_COVERAGE

186

1.1
Location : stop
Killed by : none
removed call to com/jsql/util/ThreadUtil::shutdown → NO_COVERAGE

201

1.1
Location : stop
Killed by : none
replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::stop → NO_COVERAGE

210

1.1
Location : callUrl
Killed by : none
replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::callUrl → NO_COVERAGE

214

1.1
Location : callUrl
Killed by : none
replaced return value with "" for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::callUrl → NO_COVERAGE

218

1.1
Location : getBooleanMode
Killed by : none
replaced return value with null for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::getBooleanMode → NO_COVERAGE

222

1.1
Location : getBitsUnset
Killed by : none
replaced return value with null for com/jsql/model/injection/strategy/blind/AbstractInjectionBit::getBitsUnset → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.19.1