Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,19 @@ public String getFcliCmdArgs(Map<String, Object> toolArgs) {
}

private final void addQuery(ArrayList<String> queries, String schemaPropertyName, String value) {
var fieldName = fieldsBySchemaPropertyName.getOrDefault(schemaPropertyName, schemaPropertyName);
queries.add(String.format("%s matches '%s'", fieldName, value));
// Validate that schemaPropertyName is in the known set of safe fields (do not fall back to attacker-supplied name)
if ( !fieldsBySchemaPropertyName.containsKey(schemaPropertyName) ) {
throw new FcliSimpleException("Unknown query field '%s'; allowed fields are: %s",
schemaPropertyName, String.join(", ", fieldsBySchemaPropertyName.keySet()));
}
var fieldName = fieldsBySchemaPropertyName.get(schemaPropertyName);
// Escape backslashes first, then single quotes to prevent SpEL string literal breakout
var escapedValue = value.replace("\\", "\\\\")
.replace("'", "\\'");
// Reject values containing unescaped double quotes or certain problematic characters that could break CLI token boundaries
if ( value.contains("\"") ) {
throw new FcliSimpleException("Query value contains unescaped double quote which is not allowed for CLI safety");
}
queries.add(String.format("%s matches '%s'", fieldName, escapedValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,27 @@ private String getColumnValue(ObjectNode formattedRecord, String property) {
var node = formattedRecord.get(property);
if ( node==null || node.isNull() ) { return "N/A"; }
if ( node.isArray() ) {
return JsonHelper.stream((ArrayNode)node).map(n->n.asText()).collect(Collectors.joining(","));
return JsonHelper.stream((ArrayNode)node).map(n->stripAnsiAndControlChars(n.asText())).collect(Collectors.joining(","));
}
return node.asText();
return stripAnsiAndControlChars(node.asText());
}

/**
* Strip ANSI escape sequences and control characters from string to prevent terminal injection attacks.
* Preserves newlines and tabs as they may be used intentionally in table layout.
* Removes: ESC (0x1B) and C0 control chars (0x00-0x1F) except TAB (0x09) and LF (0x0A).
*/
private String stripAnsiAndControlChars(String input) {
if ( input == null || input.isEmpty() ) { return input; }
// Remove ANSI escape sequences and C0 control chars, preserving TAB and LF
StringBuilder result = new StringBuilder();
for ( char c : input.toCharArray() ) {
// Keep printable chars and common whitespace (TAB, LF); skip ESC and other control chars
if ( (c >= 0x20 && c <= 0x7E) || c == 0x09 || c == 0x0A ) {
result.append(c);
}
}
return result.toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.cli.util.CommandGroup;
import com.fortify.cli.common.log.LogSensitivityLevel;
import com.fortify.cli.common.log.MaskValue;
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.ssc._common.output.cli.cmd.AbstractSSCJsonNodeOutputCommand;
import com.fortify.cli.ssc._common.rest.ssc.SSCUrls;
Expand All @@ -38,7 +40,8 @@ public class SSCUserCreateLocalCommand extends AbstractSSCJsonNodeOutputCommand

@Option(names = {"--username"}, required = true)
private String username;
@Option(names = {"--password"}, required = true)
@Option(names = {"--password"}, required = true, interactive = true, echo = false, arity = "0..1")
@MaskValue(sensitivity = LogSensitivityLevel.high, description = "PASSWORD")
private String password;
@Option(names = {"--firstname"})
private String firstName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.cli.util.CommandGroup;
import com.fortify.cli.common.exception.FcliSimpleException;
import com.fortify.cli.common.log.LogSensitivityLevel;
import com.fortify.cli.common.log.MaskValue;
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
import com.fortify.cli.ssc._common.output.cli.cmd.AbstractSSCJsonNodeOutputCommand;
Expand All @@ -47,7 +49,8 @@ public class SSCUserUpdateLocalCommand extends AbstractSSCJsonNodeOutputCommand
private String lastName;
@Option(names = {"--email"})
private String email;
@Option(names = {"--password"})
@Option(names = {"--password"}, interactive = true, echo = false, arity = "0..1")
@MaskValue(sensitivity = LogSensitivityLevel.high, description = "PASSWORD")
private String password;
@Option(names = {"--password-never-expires", "--pne"})
private Boolean pwNeverExpires;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.xml.XMLConstants;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
Expand Down Expand Up @@ -174,7 +175,9 @@ private final String processFpr(RawResponse r, Function<JsonNode, Boolean> consu

private final void processAuditFvdl(InputStream is, Function<JsonNode, Boolean> consumer) throws XMLStreamException {
var factory = XMLInputFactory.newInstance();
factory.setXMLResolver(null); // Prevent XML External Entity Injection
// Disable DTD processing and external entity resolution to prevent XXE attacks
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
var reader = factory.createXMLStreamReader(is);
while(reader.hasNext()) {
int eventType = reader.next();
Expand Down
Loading